Files
RealCV/DEPLOYMENT-GUIDE.md
Peter Foster 998e9a8ab8 Rename project to RealCV with new logo and font updates
- Rename all TrueCV references to RealCV across the codebase
- Add new transparent RealCV logo
- Switch from JetBrains Mono to Inter font for better number clarity
- Update solution, project files, and namespaces

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 15:07:20 +00:00

12 KiB

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
  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 - Free
Anthropic (Claude) CV parsing & analysis 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

App Service + Azure SQL

# 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:
# 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:

app.UseHsts();
app.UseHttpsRedirection();

Content Security Policy

Add to Program.cs:

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:

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:

// 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:

// In StripeService.cs - this is critical
stripeEvent = EventUtility.ConstructEvent(json, signature, _settings.WebhookSecret);

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)

// In Program.cs
builder.Services.AddApplicationInsightsTelemetry();

Budget Option: Seq + Serilog

// 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:

Set up alerts for:

  • Homepage availability
  • API health endpoint
  • Webhook endpoint

5.3 Error Tracking

Add a health check endpoint:

// 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

{
  "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


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