Deployment
Deploy your Grit application anywhere Docker runs — from a $5 VPS to managed cloud platforms. This guide walks you through a complete production deployment on a VPS, step by step.
On This Page
Choose a VPS Provider
A VPS (Virtual Private Server) is the most cost-effective way to deploy a Grit application. You get full control over the server, and a $5-10/month VPS can handle thousands of users.
| Provider | Starting At | Specs (entry) | Best For |
|---|---|---|---|
| Hetzner | $4/mo | 2 vCPU, 4GB RAM, 40GB SSD | Best price-to-performance |
| DigitalOcean | $6/mo | 1 vCPU, 1GB RAM, 25GB SSD | Great docs, managed DBs |
| Linode (Akamai) | $5/mo | 1 vCPU, 1GB RAM, 25GB SSD | Good global coverage |
| Vultr | $6/mo | 1 vCPU, 1GB RAM, 25GB SSD | Many locations, hourly billing |
Recommendation: For a Grit monorepo (Go API + 2 Next.js apps + PostgreSQL + Redis), we recommend at least 2 vCPU / 4GB RAM. Hetzner's CX22 ($4/mo) or DigitalOcean's Basic ($24/mo for 2 vCPU / 4GB) are solid choices.
Initial Server Setup
After provisioning your VPS with Ubuntu 22.04+ (recommended), SSH in as root and set up a non-root user with sudo access.
2a. SSH into your server
2b. Create a deploy user
Running everything as root is a security risk. Create a dedicated deploy user:
2c. Update packages and set timezone
Install Docker
Docker is the only dependency you need on the server. Install Docker Engine and Docker Compose:
Clone & Configure
Clone your project to the server and set up production environment variables. We recommend /opt/myapp as the deployment directory.
Production .env
Update these critical values for production. Never use default credentials in production:
APP_ENV=production APP_PORT=8080 # Database — change password! DATABASE_URL=postgres://grit:YOUR_STRONG_DB_PASSWORD@postgres:5432/myapp?sslmode=disable POSTGRES_USER=grit POSTGRES_PASSWORD=YOUR_STRONG_DB_PASSWORD POSTGRES_DB=myapp # Auth — generate a random 64-char secret # openssl rand -hex 32 JWT_SECRET=GENERATE_A_RANDOM_64_CHAR_STRING # Redis REDIS_URL=redis://redis:6379 # Frontend URLs (update with your domains) WEB_URL=https://yourdomain.com ADMIN_URL=https://admin.yourdomain.com CORS_ORIGINS=https://yourdomain.com,https://admin.yourdomain.com # Storage (Cloudflare R2 recommended for production) STORAGE_DRIVER=r2 R2_ENDPOINT=https://your-account.r2.cloudflarestorage.com R2_ACCESS_KEY=your-access-key R2_SECRET_KEY=your-secret-key R2_BUCKET=myapp-uploads # Email RESEND_API_KEY=re_your_api_key MAIL_FROM=noreply@yourdomain.com # AI (optional) AI_PROVIDER=claude AI_API_KEY=sk-ant-xxxxx AI_MODEL=claude-sonnet-4-5-20250929 # Disable GORM Studio in production GORM_STUDIO_ENABLED=false
Security tip: Generate a strong JWT secret with: openssl rand -hex 32. Never commit your .env file to git.
Environment Variables Reference
| Variable | Required | Notes |
|---|---|---|
| APP_ENV | Yes | Set to "production" |
| DATABASE_URL | Yes | PostgreSQL connection string |
| JWT_SECRET | Yes | Random 64+ character string. Never reuse. |
| REDIS_URL | Yes | Redis connection URL |
| CORS_ORIGINS | Yes | Comma-separated allowed frontend domains |
| WEB_URL | Yes | Your web app URL (for CORS and redirects) |
| ADMIN_URL | Yes | Your admin panel URL |
| RESEND_API_KEY | If emails | Resend API key for transactional emails |
| STORAGE_DRIVER | If uploads | "r2", "b2", or "minio" for file storage |
| GORM_STUDIO_ENABLED | No | Set to "false" in production |
Deploy with Docker Compose
Grit includes a production-ready docker-compose.prod.yml that builds optimized containers for the Go API and Next.js apps, plus PostgreSQL and Redis.
Expected output: you should see all containers running (api, web, admin, postgres, redis). The first build may take a few minutes as Docker builds the Go binary and Next.js apps.
What the production build does
View logs
Set Up SSL with Caddy
Caddy is a modern web server that automatically provisions SSL certificates from Let's Encrypt. It's the easiest way to get HTTPS working.
6a. Install Caddy
6b. Configure the Caddyfile
Before editing, make sure your DNS A records point to your server's IP address. Caddy will automatically obtain SSL certificates once DNS is configured.
# Go API
api.yourdomain.com {
reverse_proxy localhost:8080
}
# Next.js Web App
yourdomain.com {
reverse_proxy localhost:3000
}
# Admin Panel
admin.yourdomain.com {
reverse_proxy localhost:3001
}6c. Start Caddy
Caddy will automatically obtain and renew SSL certificates from Let's Encrypt. After a few seconds, your site will be live at https://yourdomain.com.
DNS Records
Add these DNS A records at your domain registrar or Cloudflare:
| Type | Name | Value |
|---|---|---|
| A | @ | your-server-ip |
| A | api | your-server-ip |
| A | admin | your-server-ip |
Configure Firewall (UFW)
Lock down your server to only expose the ports you need. UFW (Uncomplicated Firewall) makes this simple:
This blocks all inbound traffic except SSH (22), HTTP (80), and HTTPS (443). Database (5432) and Redis (6379) ports are only accessible from within Docker's network.
Automated Deployments (CI/CD)
Automate deployments so that every push to main deploys to production. Here's a GitHub Actions workflow that SSHs into your server and redeploys:
name: Deploy to VPS
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Deploy via SSH
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.VPS_HOST }}
username: deploy
key: ${{ secrets.VPS_SSH_KEY }}
script: |
cd /opt/myapp
git pull origin main
docker compose -f docker-compose.prod.yml up -d --build
docker image prune -fSet up GitHub Secrets
In your GitHub repo, go to Settings → Secrets → Actions and add:
| Secret | Value |
|---|---|
| VPS_HOST | Your server IP (e.g., 164.90.131.42) |
| VPS_SSH_KEY | The deploy user's private SSH key (contents of ~/.ssh/id_ed25519) |
Tip: Generate a dedicated deploy key for CI/CD instead of using your personal SSH key:ssh-keygen -t ed25519 -f deploy_key -C "github-actions". Add the public key to ~/.ssh/authorized_keys on the server.
Database Backups
Always back up your production database. Losing data is irreversible.
Manual backup
Automated daily backups (cron)
Set up a cron job to backup daily and keep the last 7 days:
# Daily DB backup at 3 AM, keep last 7 days 0 3 * * * cd /opt/myapp && docker compose -f docker-compose.prod.yml exec -T postgres pg_dump -U grit myapp | gzip > /backups/myapp-$(date +\%Y\%m\%d).sql.gz && find /backups -name "myapp-*.sql.gz" -mtime +7 -delete
Managed database backups
If you use a managed PostgreSQL service (DigitalOcean Managed Databases, Supabase, Neon, etc.), backups are handled automatically. This is the recommended approach for important production workloads.
Monitoring & Logging
Grit's Go API includes a built-in request logger middleware. For production, add proper monitoring to catch issues before your users do:
Docker Logs
Built-in. Use "docker compose logs -f api" to stream API logs in real time. Combine with journald for persistence.
Uptime Kuma
Self-hosted uptime monitoring. Set up health checks against your API endpoint. Get notified via email, Slack, or Telegram.
Grafana + Prometheus
For advanced metrics. Add a /metrics endpoint to your Go API and visualize request rates, latency, and error rates.
Sentry
Error tracking for both Go API and Next.js frontends. Captures stack traces, context, and user info.
Better Stack
Managed log aggregation and uptime monitoring. Stream Docker logs for search, alerting, and dashboards.
Zero-Downtime Updates
To update your running application with minimal downtime, use Docker's rolling update capability:
Docker Compose detects which services have changed and only rebuilds and restarts those. PostgreSQL and Redis keep running throughout. The typical downtime for an API update is under 5 seconds.
For true zero-downtime: Use Docker Compose's health checks combined with Caddy. Caddy will only route traffic to healthy containers, so the old container serves requests until the new one is ready.
Production Checklist
Before going live, make sure you have completed these steps:
Security
Infrastructure
Performance
Alternative: Cloud Platforms
If you prefer managed infrastructure over a VPS, these platforms support Grit's architecture. Deploy the Go API and Next.js apps as separate services:
Railway
Deploy from GitHub with zero config. Supports Go, Node.js, PostgreSQL, and Redis as managed services.
Best for solo developers and small teams.
Render
Deploy Go as a Web Service, Next.js as a Web Service. Managed PostgreSQL and Redis available.
Best for side projects and startups.
Fly.io
Container-based deployment with edge computing. Deploy Docker images directly. Built-in Postgres and Redis.
Best for apps needing global edge distribution.
Coolify (Self-Hosted)
Open-source Heroku alternative you host on your own VPS. Docker-based, built-in databases, auto SSL. Free.
Best for PaaS convenience at VPS pricing.