Documentation Index
Fetch the complete documentation index at: https://docs.aspfox.com/llms.txt
Use this file to discover all available pages before exploring further.
This guide uses Ubuntu 22.04. Every command is exact and copy-paste ready.
Provision a VPS
Any provider works: DigitalOcean, Hetzner, Linode, Vultr. Minimum recommended: 2 vCPU, 2 GB RAM, 20 GB SSD. Ubuntu 22.04 LTS.After provisioning, SSH in as root:Create a non-root user:adduser deploy
usermod -aG sudo deploy
rsync --archive --chown=deploy:deploy ~/.ssh /home/deploy
Switch to the deploy user for the rest of this guide: Install Docker
# Install prerequisites
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg
# Add Docker's GPG key
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
| sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
# Add the Docker repository
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# Install Docker Engine and Compose plugin
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io \
docker-buildx-plugin docker-compose-plugin
# Allow deploy user to run Docker without sudo
sudo usermod -aG docker deploy
newgrp docker
# Verify
docker --version
docker compose version
Clone your repository
cd /opt
sudo mkdir acme
sudo chown deploy:deploy acme
cd acme
git clone https://github.com/yourorg/youracme-repo.git .
Configure environment variables
cp .env.example .env
nano .env
Fill in all production values. Key differences from local development:ASPNETCORE_ENVIRONMENT=Production
DATABASE_URL=Host=postgres;Port=5432;Database=acme;Username=acme;Password=<strong-password>
REDIS_URL=redis:6379
APP_URL=https://yourdomain.com
API_URL=https://api.yourdomain.com
VITE_API_URL=https://api.yourdomain.com
Generate production JWT keys
openssl genrsa -out private.pem 4096
openssl rsa -in private.pem -pubout -out public.pem
base64 -w 0 private.pem
base64 -w 0 public.pem
Copy the base64 output into .env as JWT_PRIVATE_KEY and JWT_PUBLIC_KEY, then delete the key files:rm private.pem public.pem
Start services with production Docker Compose
docker compose -f docker-compose.prod.yml up -d
Check that all containers are running:docker compose -f docker-compose.prod.yml ps
You should see api, frontend, postgres, and redis all in running state.Run migrations and seed
docker compose -f docker-compose.prod.yml exec api \
dotnet ef database update \
--project src/Acme.Infrastructure \
--startup-project src/Acme.Api
docker compose -f docker-compose.prod.yml exec api \
dotnet run --project src/Acme.Api -- --seed-only
Install and configure Nginx
sudo apt-get install -y nginx
Create the Nginx configuration:sudo nano /etc/nginx/sites-available/acme
Paste this configuration (replace yourdomain.com and api.yourdomain.com with your actual domains):# API
server {
listen 80;
server_name api.yourdomain.com;
location / {
proxy_pass http://localhost:5000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection keep-alive;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
proxy_read_timeout 300s;
proxy_connect_timeout 75s;
}
}
# Frontend
server {
listen 80;
server_name yourdomain.com;
location / {
proxy_pass http://localhost:5173;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Enable the configuration:sudo ln -s /etc/nginx/sites-available/acme /etc/nginx/sites-enabled/
sudo nginx -t # Test the configuration — must print "syntax is ok"
sudo systemctl reload nginx
Set up SSL with Let's Encrypt
sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx \
-d yourdomain.com \
-d api.yourdomain.com \
--non-interactive \
--agree-tos \
--email your@email.com
Certbot modifies the Nginx configuration to add HTTPS and sets up automatic renewal. Verify:sudo certbot renew --dry-run
Configure Stripe production webhook
Now that your API has a public HTTPS URL:
- Stripe Dashboard → Developers → Webhooks → Add endpoint
- URL:
https://api.yourdomain.com/api/v1/webhooks/stripe
- Events: checkout.session.completed, customer.subscription.updated, customer.subscription.deleted, invoice.payment_failed, invoice.payment_succeeded
- Copy signing secret → update
STRIPE_WEBHOOK_SECRET in .env
- Restart the API:
docker compose -f docker-compose.prod.yml restart api
Verify
curl https://api.yourdomain.com/health
# Should return: {"status":"healthy"}
Open https://yourdomain.com in a browser and log in with admin credentials.
Keeping the application updated
To deploy a new version:
cd /opt/acme
git pull origin main
docker compose -f docker-compose.prod.yml build api frontend
docker compose -f docker-compose.prod.yml up -d
# Run migrations if schema changed:
docker compose -f docker-compose.prod.yml exec api \
dotnet ef database update \
--project src/Acme.Infrastructure \
--startup-project src/Acme.Api
Viewing production logs
# All services
docker compose -f docker-compose.prod.yml logs -f
# API only
docker compose -f docker-compose.prod.yml logs -f api