grit deploy
The deploy command, end to end.
Time to ship. grit deploy takes your local project and runs it on a VPS ā Docker, Traefik for HTTPS, your domain, a functioning Postgres. One command. This lesson covers what it does and the prerequisites.
What the command does
$grit deploy --host root@1.2.3.4 --domain api.acme.com
It runs (in this order):
- SSH's to the host, installs Docker + Docker Compose if missing
- Builds your project locally + ships the image to the host
- Copies
docker-compose.prod.yml+ your.env.production - Provisions Traefik with Let's Encrypt for HTTPS
- Starts Postgres + Redis + your API behind Traefik with the domain you specified
- Health-checks the deployment
- Prints the URL ā typically
https://api.acme.com/api/health
Prerequisites
- A VPS (Hetzner / DigitalOcean / Linode / Vultr ā anywhere with SSH + a public IP). $4/month tier works.
- SSH key access (no password auth)
- A domain you control + DNS access
- An
.env.productionin your project with real secrets
Setting up DNS
Point an A record at your VPS IP before running deploy. Otherwise Let's Encrypt can't verify domain ownership and HTTPS provisioning fails.
api.acme.com ā A ā 1.2.3.4 (your VPS IP)
.env.production ā what changes from .env
Your dev .env uses localhost services + Mailhog + MinIO. Production uses real services:
APP_ENV=productionAPP_PORT=8080APP_URL=https://api.acme.comDATABASE_URL=postgres://grit:STRONG_PASSWORD@postgres:5432/myapp?sslmode=disableREDIS_URL=redis://redis:6379JWT_SECRET=<openssl rand -hex 32># Real emailRESEND_API_KEY=re_live_...MAIL_FROM=noreply@acme.com# Real storageSTORAGE_DRIVER=r2R2_ACCOUNT_ID=...R2_ACCESS_KEY=...R2_SECRET_KEY=...R2_BUCKET=acme-prod# Sentinel + Pulse ā strong passwords required in productionSENTINEL_PASSWORD=<openssl rand -hex 16>SENTINEL_SECRET_KEY=<openssl rand -hex 32>PULSE_PASSWORD=<openssl rand -hex 16># CORSCORS_ORIGINS=https://app.acme.com,https://acme.com
.env.production to git. It contains every secret. Add it to .gitignore (Grit's scaffold already does). Store the file somewhere safe (1Password, Bitwarden, vault).What gets deployed
on the VPS:/opt/myapp/āāā docker-compose.prod.yml ā Traefik + Postgres + Redis + APIāāā .env ā your .env.production renamedāāā traefik/ ā cert storage, configāāā postgres-data/ ā Postgres volume
Docker Compose handles all process management. systemd is optional; the compose stack starts on boot via restart: unless-stopped.
Subsequent deploys
# After the first deploy, subsequent ones are this:$git push # commit your changes$grit deploy --host root@1.2.3.4 # ship them
Grit rebuilds the image, ships it, restarts the API container with a rolling update. Zero downtime if you wire health checks (see next lesson).
Alternatives
- Dokploy ā UI for the same docker-compose deploys. Worth a look if you want a dashboard.
- Fly.io / Railway / Render ā managed PaaS. More expensive than a VPS but zero config.
- Kubernetes ā for products at scale. Grit ships example manifests; for <10K users you don't need it.
Quick check
Try it
Spin up a $4 VPS (Hetzner / DigitalOcean / etc) and deploy your bench-api to it.
- Create the VPS, note its IP.
- Point an A record at it. Wait ~5 minutes for DNS to propagate.
- Create
.env.productionwith your real secrets. - Run
grit deploy --host root@<IP> --domain api.<your-domain> - Hit
https://api.<your-domain>/api/healthfrom your phone (not localhost!) to confirm.
Paste the response (with the real domain) in notes.md.
What's next
Last lesson ā the env vars checklist. What MUST be set in production before you ship to real users.
Spot a typo? Have an idea?
Help us improve this lesson. One click opens a GitHub issue with the lesson URL pre-filled ā suggest clearer wording, report a bug, or request more depth. The course keeps improving thanks to learners like you.
Suggest an improvement on GitHub