- 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>
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)
- Create a Stripe account at stripe.com
- Complete business verification (required for live payments)
- Create two Products in Stripe Dashboard:
- Professional: £49/month recurring
- Enterprise: £199/month recurring
- Copy the Price IDs (start with
price_) to your config - Configure the Customer Portal:
- Dashboard → Settings → Billing → Customer Portal
- Enable: Update payment methods, Cancel subscriptions, View invoices
- 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
- 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
- Register a domain (Cloudflare, Namecheap, or similar)
- Set up a professional email:
- Budget option: Zoho Mail free tier (5 users)
- Better option: Google Workspace (£5/user/month)
- Configure SPF, DKIM, DMARC records for email deliverability
Phase 2: Infrastructure Setup
Option A: Azure (Recommended for .NET)
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:
- Ubuntu 22.04 LTS
- Install Docker and Docker Compose
- Use the existing
docker-compose.ymlwith 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
- Use strong passwords (20+ characters, random)
- Enable Azure SQL firewall - allow only App Service IP
- Enable Transparent Data Encryption (on by default in Azure)
- Regular backups - Azure does this automatically (7-day retention on Basic tier)
3.3 File Storage Security
CV files contain sensitive data:
- Private container - never allow public blob access
- SAS tokens - generate time-limited URLs for downloads
- Encryption at rest - enabled by default in Azure Storage
- 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);
Phase 4: Compliance & Legal
4.1 GDPR Compliance (Required for UK/EU)
-
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)
-
Cookie Consent - add banner for analytics cookies
-
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)
-
Right to Deletion - implement account deletion feature
-
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:
- UptimeRobot - 50 free monitors
- Freshping - 50 free monitors
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