# 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 # 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=;Encrypt=True; ConnectionStrings__HangfireConnection= 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= 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(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(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*