448 lines
12 KiB
Markdown
448 lines
12 KiB
Markdown
|
|
# TrueCV Production Deployment Guide
|
||
|
|
|
||
|
|
A low-budget guide to launching TrueCV as a professional, secure offering.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Budget Summary
|
||
|
|
|
||
|
|
| Component | Monthly Cost | Notes |
|
||
|
|
|-----------|-------------|-------|
|
||
|
|
| Azure App Service (B1) | ~£10 | 1.75GB RAM, custom domain, SSL |
|
||
|
|
| Azure SQL (Basic) | ~£4 | 2GB, 5 DTUs - upgrade as needed |
|
||
|
|
| Azure Blob Storage | ~£1 | Pay per GB stored |
|
||
|
|
| Domain name | ~£10/year | .com or .co.uk |
|
||
|
|
| **Total** | **~£15-20/month** | Scales with usage |
|
||
|
|
|
||
|
|
Alternative: A single £5-10/month VPS (Hetzner, DigitalOcean) can run everything if you're comfortable with Linux administration.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Phase 1: Pre-Launch Checklist
|
||
|
|
|
||
|
|
### 1.1 Stripe Setup (Required for Payments)
|
||
|
|
|
||
|
|
1. Create a Stripe account at [stripe.com](https://stripe.com)
|
||
|
|
2. Complete business verification (required for live payments)
|
||
|
|
3. Create two Products in Stripe Dashboard:
|
||
|
|
- **Professional**: £49/month recurring
|
||
|
|
- **Enterprise**: £199/month recurring
|
||
|
|
4. Copy the Price IDs (start with `price_`) to your config
|
||
|
|
5. Configure the Customer Portal:
|
||
|
|
- Dashboard → Settings → Billing → Customer Portal
|
||
|
|
- Enable: Update payment methods, Cancel subscriptions, View invoices
|
||
|
|
6. Set up webhook endpoint (after deployment):
|
||
|
|
- Dashboard → Developers → Webhooks → Add endpoint
|
||
|
|
- URL: `https://yourdomain.com/api/stripe/webhook`
|
||
|
|
- Events: `checkout.session.completed`, `customer.subscription.updated`, `customer.subscription.deleted`, `invoice.payment_failed`
|
||
|
|
7. Copy the webhook signing secret to your config
|
||
|
|
|
||
|
|
### 1.2 External API Keys
|
||
|
|
|
||
|
|
| Service | Purpose | How to Get |
|
||
|
|
|---------|---------|-----------|
|
||
|
|
| Companies House | Company verification | [developer.company-information.service.gov.uk](https://developer.company-information.service.gov.uk) - Free |
|
||
|
|
| Anthropic (Claude) | CV parsing & analysis | [console.anthropic.com](https://console.anthropic.com) - Pay per use |
|
||
|
|
|
||
|
|
### 1.3 Domain & Email
|
||
|
|
|
||
|
|
1. Register a domain (Cloudflare, Namecheap, or similar)
|
||
|
|
2. Set up a professional email:
|
||
|
|
- Budget option: Zoho Mail free tier (5 users)
|
||
|
|
- Better option: Google Workspace (£5/user/month)
|
||
|
|
3. Configure SPF, DKIM, DMARC records for email deliverability
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Phase 2: Infrastructure Setup
|
||
|
|
|
||
|
|
### Option A: Azure (Recommended for .NET)
|
||
|
|
|
||
|
|
#### App Service + Azure SQL
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Install Azure CLI, then:
|
||
|
|
az login
|
||
|
|
|
||
|
|
# Create resource group
|
||
|
|
az group create --name truecv-prod --location uksouth
|
||
|
|
|
||
|
|
# Create App Service plan (B1 = ~£10/month)
|
||
|
|
az appservice plan create \
|
||
|
|
--name truecv-plan \
|
||
|
|
--resource-group truecv-prod \
|
||
|
|
--sku B1 \
|
||
|
|
--is-linux
|
||
|
|
|
||
|
|
# Create web app
|
||
|
|
az webapp create \
|
||
|
|
--name truecv-app \
|
||
|
|
--resource-group truecv-prod \
|
||
|
|
--plan truecv-plan \
|
||
|
|
--runtime "DOTNETCORE:8.0"
|
||
|
|
|
||
|
|
# Create SQL Server
|
||
|
|
az sql server create \
|
||
|
|
--name truecv-sql \
|
||
|
|
--resource-group truecv-prod \
|
||
|
|
--location uksouth \
|
||
|
|
--admin-user truecvadmin \
|
||
|
|
--admin-password <STRONG_PASSWORD>
|
||
|
|
|
||
|
|
# Create database (Basic = ~£4/month)
|
||
|
|
az sql db create \
|
||
|
|
--name truecv-db \
|
||
|
|
--server truecv-sql \
|
||
|
|
--resource-group truecv-prod \
|
||
|
|
--service-objective Basic
|
||
|
|
|
||
|
|
# Create storage account for CV files
|
||
|
|
az storage account create \
|
||
|
|
--name truecvstorage \
|
||
|
|
--resource-group truecv-prod \
|
||
|
|
--location uksouth \
|
||
|
|
--sku Standard_LRS
|
||
|
|
```
|
||
|
|
|
||
|
|
#### Environment Variables (App Service Configuration)
|
||
|
|
|
||
|
|
Set these in Azure Portal → App Service → Configuration → Application settings:
|
||
|
|
|
||
|
|
```
|
||
|
|
ConnectionStrings__DefaultConnection=Server=truecv-sql.database.windows.net;Database=truecv-db;User Id=truecvadmin;Password=<PASSWORD>;Encrypt=True;
|
||
|
|
ConnectionStrings__HangfireConnection=<SAME_AS_ABOVE>
|
||
|
|
Stripe__SecretKey=sk_live_xxx
|
||
|
|
Stripe__PublishableKey=pk_live_xxx
|
||
|
|
Stripe__WebhookSecret=whsec_xxx
|
||
|
|
Stripe__PriceIds__Professional=price_xxx
|
||
|
|
Stripe__PriceIds__Enterprise=price_xxx
|
||
|
|
Anthropic__ApiKey=sk-ant-xxx
|
||
|
|
CompaniesHouse__ApiKey=xxx
|
||
|
|
AzureBlob__ConnectionString=<FROM_STORAGE_ACCOUNT>
|
||
|
|
AzureBlob__ContainerName=cvfiles
|
||
|
|
```
|
||
|
|
|
||
|
|
### Option B: VPS (Budget Alternative)
|
||
|
|
|
||
|
|
A £5-10/month VPS from Hetzner, DigitalOcean, or Vultr can run everything:
|
||
|
|
|
||
|
|
1. Ubuntu 22.04 LTS
|
||
|
|
2. Install Docker and Docker Compose
|
||
|
|
3. Use the existing `docker-compose.yml` with modifications:
|
||
|
|
|
||
|
|
```yaml
|
||
|
|
# docker-compose.prod.yml
|
||
|
|
version: '3.8'
|
||
|
|
services:
|
||
|
|
web:
|
||
|
|
build: .
|
||
|
|
ports:
|
||
|
|
- "5000:8080"
|
||
|
|
environment:
|
||
|
|
- ASPNETCORE_ENVIRONMENT=Production
|
||
|
|
- ConnectionStrings__DefaultConnection=Server=db;Database=TrueCV;User Id=sa;Password=${DB_PASSWORD};TrustServerCertificate=True
|
||
|
|
depends_on:
|
||
|
|
- db
|
||
|
|
restart: always
|
||
|
|
|
||
|
|
db:
|
||
|
|
image: mcr.microsoft.com/mssql/server:2022-latest
|
||
|
|
environment:
|
||
|
|
- ACCEPT_EULA=Y
|
||
|
|
- SA_PASSWORD=${DB_PASSWORD}
|
||
|
|
volumes:
|
||
|
|
- sqldata:/var/opt/mssql
|
||
|
|
restart: always
|
||
|
|
|
||
|
|
caddy: # Reverse proxy with automatic HTTPS
|
||
|
|
image: caddy:2
|
||
|
|
ports:
|
||
|
|
- "80:80"
|
||
|
|
- "443:443"
|
||
|
|
volumes:
|
||
|
|
- ./Caddyfile:/etc/caddy/Caddyfile
|
||
|
|
- caddy_data:/data
|
||
|
|
restart: always
|
||
|
|
|
||
|
|
volumes:
|
||
|
|
sqldata:
|
||
|
|
caddy_data:
|
||
|
|
```
|
||
|
|
|
||
|
|
```
|
||
|
|
# Caddyfile
|
||
|
|
yourdomain.com {
|
||
|
|
reverse_proxy web:5000
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Phase 3: Security Hardening
|
||
|
|
|
||
|
|
### 3.1 Application Security (Critical)
|
||
|
|
|
||
|
|
#### Secrets Management
|
||
|
|
- **Never** commit secrets to git
|
||
|
|
- Use Azure Key Vault or environment variables
|
||
|
|
- Rotate API keys quarterly
|
||
|
|
|
||
|
|
#### HTTPS Enforcement
|
||
|
|
Already configured in `Program.cs`:
|
||
|
|
```csharp
|
||
|
|
app.UseHsts();
|
||
|
|
app.UseHttpsRedirection();
|
||
|
|
```
|
||
|
|
|
||
|
|
#### Content Security Policy
|
||
|
|
Add to `Program.cs`:
|
||
|
|
```csharp
|
||
|
|
app.Use(async (context, next) =>
|
||
|
|
{
|
||
|
|
context.Response.Headers.Append("X-Content-Type-Options", "nosniff");
|
||
|
|
context.Response.Headers.Append("X-Frame-Options", "DENY");
|
||
|
|
context.Response.Headers.Append("X-XSS-Protection", "1; mode=block");
|
||
|
|
context.Response.Headers.Append("Referrer-Policy", "strict-origin-when-cross-origin");
|
||
|
|
context.Response.Headers.Append("Permissions-Policy", "camera=(), microphone=(), geolocation=()");
|
||
|
|
await next();
|
||
|
|
});
|
||
|
|
```
|
||
|
|
|
||
|
|
#### Rate Limiting
|
||
|
|
Add to `Program.cs`:
|
||
|
|
```csharp
|
||
|
|
builder.Services.AddRateLimiter(options =>
|
||
|
|
{
|
||
|
|
options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(context =>
|
||
|
|
RateLimitPartition.GetFixedWindowLimiter(
|
||
|
|
partitionKey: context.User.Identity?.Name ?? context.Request.Headers.Host.ToString(),
|
||
|
|
factory: _ => new FixedWindowRateLimiterOptions
|
||
|
|
{
|
||
|
|
AutoReplenishment = true,
|
||
|
|
PermitLimit = 100,
|
||
|
|
Window = TimeSpan.FromMinutes(1)
|
||
|
|
}));
|
||
|
|
});
|
||
|
|
|
||
|
|
// In middleware pipeline
|
||
|
|
app.UseRateLimiter();
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3.2 Database Security
|
||
|
|
|
||
|
|
1. **Use strong passwords** (20+ characters, random)
|
||
|
|
2. **Enable Azure SQL firewall** - allow only App Service IP
|
||
|
|
3. **Enable Transparent Data Encryption** (on by default in Azure)
|
||
|
|
4. **Regular backups** - Azure does this automatically (7-day retention on Basic tier)
|
||
|
|
|
||
|
|
### 3.3 File Storage Security
|
||
|
|
|
||
|
|
CV files contain sensitive data:
|
||
|
|
|
||
|
|
1. **Private container** - never allow public blob access
|
||
|
|
2. **SAS tokens** - generate time-limited URLs for downloads
|
||
|
|
3. **Encryption at rest** - enabled by default in Azure Storage
|
||
|
|
4. **Consider encryption at application level** for extra protection
|
||
|
|
|
||
|
|
### 3.4 Authentication Security
|
||
|
|
|
||
|
|
Already using ASP.NET Identity with good defaults. Verify these settings:
|
||
|
|
|
||
|
|
```csharp
|
||
|
|
// In Program.cs - identity configuration
|
||
|
|
builder.Services.Configure<IdentityOptions>(options =>
|
||
|
|
{
|
||
|
|
options.Password.RequiredLength = 12;
|
||
|
|
options.Password.RequireDigit = true;
|
||
|
|
options.Password.RequireLowercase = true;
|
||
|
|
options.Password.RequireUppercase = true;
|
||
|
|
options.Password.RequireNonAlphanumeric = true;
|
||
|
|
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(15);
|
||
|
|
options.Lockout.MaxFailedAccessAttempts = 5;
|
||
|
|
});
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3.5 Stripe Webhook Security
|
||
|
|
|
||
|
|
Already implemented - verify signature on every webhook:
|
||
|
|
```csharp
|
||
|
|
// In StripeService.cs - this is critical
|
||
|
|
stripeEvent = EventUtility.ConstructEvent(json, signature, _settings.WebhookSecret);
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Phase 4: Compliance & Legal
|
||
|
|
|
||
|
|
### 4.1 GDPR Compliance (Required for UK/EU)
|
||
|
|
|
||
|
|
1. **Privacy Policy** - create and link in footer
|
||
|
|
- What data you collect (CVs, email, payment info)
|
||
|
|
- How long you retain it
|
||
|
|
- User rights (access, deletion, portability)
|
||
|
|
- Third parties (Stripe, Anthropic, Companies House)
|
||
|
|
|
||
|
|
2. **Cookie Consent** - add banner for analytics cookies
|
||
|
|
|
||
|
|
3. **Data Retention Policy**:
|
||
|
|
- CVs: Delete after 30 days or on user request
|
||
|
|
- Accounts: Retain while active, delete 90 days after closure
|
||
|
|
- Payment data: Stripe handles this (PCI compliant)
|
||
|
|
|
||
|
|
4. **Right to Deletion** - implement account deletion feature
|
||
|
|
|
||
|
|
5. **Data Processing Agreement** - required if you have business customers
|
||
|
|
|
||
|
|
### 4.2 Terms of Service
|
||
|
|
|
||
|
|
Cover:
|
||
|
|
- Service description and limitations
|
||
|
|
- Acceptable use policy
|
||
|
|
- Payment terms and refund policy
|
||
|
|
- Liability limitations
|
||
|
|
- Dispute resolution
|
||
|
|
|
||
|
|
### 4.3 PCI Compliance
|
||
|
|
|
||
|
|
Stripe Checkout handles card data - you never touch it. This puts you in **PCI SAQ-A** (simplest level):
|
||
|
|
- Use only Stripe Checkout or Elements
|
||
|
|
- Serve pages over HTTPS
|
||
|
|
- Don't store card numbers
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Phase 5: Monitoring & Operations
|
||
|
|
|
||
|
|
### 5.1 Application Monitoring
|
||
|
|
|
||
|
|
#### Free Option: Application Insights (Azure)
|
||
|
|
```csharp
|
||
|
|
// In Program.cs
|
||
|
|
builder.Services.AddApplicationInsightsTelemetry();
|
||
|
|
```
|
||
|
|
|
||
|
|
#### Budget Option: Seq + Serilog
|
||
|
|
```csharp
|
||
|
|
// Already using Serilog - add Seq sink
|
||
|
|
Log.Logger = new LoggerConfiguration()
|
||
|
|
.WriteTo.Seq("http://localhost:5341") // Self-hosted Seq
|
||
|
|
.CreateLogger();
|
||
|
|
```
|
||
|
|
|
||
|
|
### 5.2 Uptime Monitoring
|
||
|
|
|
||
|
|
Free options:
|
||
|
|
- [UptimeRobot](https://uptimerobot.com) - 50 free monitors
|
||
|
|
- [Freshping](https://freshping.io) - 50 free monitors
|
||
|
|
|
||
|
|
Set up alerts for:
|
||
|
|
- Homepage availability
|
||
|
|
- API health endpoint
|
||
|
|
- Webhook endpoint
|
||
|
|
|
||
|
|
### 5.3 Error Tracking
|
||
|
|
|
||
|
|
Add a health check endpoint:
|
||
|
|
```csharp
|
||
|
|
// In Program.cs
|
||
|
|
app.MapGet("/health", () => Results.Ok(new { status = "healthy", timestamp = DateTime.UtcNow }));
|
||
|
|
```
|
||
|
|
|
||
|
|
### 5.4 Backup Strategy
|
||
|
|
|
||
|
|
| Component | Backup Method | Frequency |
|
||
|
|
|-----------|--------------|-----------|
|
||
|
|
| Database | Azure automated backups | Continuous (7-day retention) |
|
||
|
|
| CV files | Azure Blob redundancy (LRS) | Automatic |
|
||
|
|
| Config | Git repository | On change |
|
||
|
|
|
||
|
|
For VPS: Set up daily database dumps to offsite storage.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Phase 6: Launch Checklist
|
||
|
|
|
||
|
|
### Pre-Launch (1 week before)
|
||
|
|
|
||
|
|
- [ ] All environment variables configured
|
||
|
|
- [ ] Database migrations applied
|
||
|
|
- [ ] Stripe products created and tested
|
||
|
|
- [ ] Webhook endpoint configured and tested
|
||
|
|
- [ ] SSL certificate active
|
||
|
|
- [ ] Privacy Policy and Terms published
|
||
|
|
- [ ] Test complete user journey (signup → payment → CV check)
|
||
|
|
- [ ] Test subscription cancellation flow
|
||
|
|
- [ ] Error pages customised (404, 500)
|
||
|
|
|
||
|
|
### Launch Day
|
||
|
|
|
||
|
|
- [ ] Switch Stripe to live mode (change API keys)
|
||
|
|
- [ ] Verify webhook is receiving live events
|
||
|
|
- [ ] Monitor error logs closely
|
||
|
|
- [ ] Test a real payment (refund yourself)
|
||
|
|
|
||
|
|
### Post-Launch (First Week)
|
||
|
|
|
||
|
|
- [ ] Monitor for errors daily
|
||
|
|
- [ ] Check Stripe dashboard for failed payments
|
||
|
|
- [ ] Respond to support queries within 24 hours
|
||
|
|
- [ ] Gather user feedback
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Phase 7: Scaling (When Needed)
|
||
|
|
|
||
|
|
Start small and scale based on actual usage:
|
||
|
|
|
||
|
|
| Trigger | Action | Cost Impact |
|
||
|
|
|---------|--------|-------------|
|
||
|
|
| Response time > 2s | Upgrade App Service to B2/B3 | +£10-30/month |
|
||
|
|
| Database DTU > 80% | Upgrade to Standard S0 | +£15/month |
|
||
|
|
| Storage > 5GB | Already pay-per-use | Minimal |
|
||
|
|
| > 1000 users | Add Redis for caching | +£15/month |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Quick Reference: Configuration Files
|
||
|
|
|
||
|
|
### appsettings.Production.json
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"Logging": {
|
||
|
|
"LogLevel": {
|
||
|
|
"Default": "Warning",
|
||
|
|
"Microsoft.AspNetCore": "Warning"
|
||
|
|
}
|
||
|
|
},
|
||
|
|
"AllowedHosts": "yourdomain.com;www.yourdomain.com"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
Sensitive values should be in environment variables, not this file.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Support & Resources
|
||
|
|
|
||
|
|
- [Azure App Service Docs](https://learn.microsoft.com/en-us/azure/app-service/)
|
||
|
|
- [Stripe Documentation](https://stripe.com/docs)
|
||
|
|
- [ASP.NET Core Security](https://learn.microsoft.com/en-us/aspnet/core/security/)
|
||
|
|
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Estimated Time to Launch
|
||
|
|
|
||
|
|
| Phase | Effort |
|
||
|
|
|-------|--------|
|
||
|
|
| Stripe setup | 1-2 hours |
|
||
|
|
| Infrastructure | 2-4 hours |
|
||
|
|
| Security hardening | 2-3 hours |
|
||
|
|
| Legal pages | 2-4 hours (use templates) |
|
||
|
|
| Testing | 2-4 hours |
|
||
|
|
| **Total** | **1-2 days** |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
*Document version: 1.0 | Last updated: January 2026*
|