Dokploy is a self-hosted Platform-as-a-Service (PaaS) that simplifies deploying applications on your own VPS. It provides a web-based dashboard for managing deployments, databases, domains, and SSL certificates -- similar to platforms like Heroku or Railway, but running on infrastructure you control.
This guide walks you through deploying GritCMS on a VPS using Dokploy with Docker Compose.
Prerequisites
- A VPS running Ubuntu 20.04+ (or Debian 10+, Fedora 40, CentOS 8-9)
- Minimum 2 GB RAM (4 GB recommended)
- At least 30 GB disk space
- A domain name pointed to your VPS IP address (e.g.,
yourdomain.com) - Your GritCMS code in a GitHub, GitLab, or Gitea repository
Architecture
┌──────────────────────────────────────────────┐
│ Dokploy │
│ (Traefik reverse proxy) │
│ │
yourdomain.com ──►│ web (Next.js) :3000 │
admin.yourdomain ──►│ admin (Next.js) :3000 │
api.yourdomain ──►│ api (Go/Gin) :8080 │
│ │
│ postgres (PostgreSQL 16) :5432 │
│ redis (Redis 7) :6379 │
│ minio (MinIO S3) :9000 │
└──────────────────────────────────────────────┘Dokploy's built-in Traefik reverse proxy routes traffic by domain name, terminates SSL via Let's Encrypt, and forwards requests to containers on the internal Docker network.
Step 1: Install Dokploy on Your VPS
SSH into your VPS and run the Dokploy installation script:
ssh root@your-vps-ip
curl -sSL https://dokploy.com/install.sh | shAfter installation (~5 minutes), access Dokploy at:
http://your-vps-ip:3000Create your Dokploy admin account on the setup page.
Step 2: Point DNS to Your VPS
Add these A records in your DNS provider:
| Record | Type | Value |
|---|---|---|
@ (or yourdomain.com) | A | Your VPS IP |
admin | A | Your VPS IP |
api | A | Your VPS IP |
Wait for DNS propagation (usually 5-15 minutes).
Step 3: Create a Project in Dokploy
- Log into the Dokploy dashboard
- Click Projects in the sidebar
- Click Create Project and name it
GritCMS
Step 4: Add Docker Compose Service
GritCMS uses a single Docker Compose file that defines all services. This is the simplest way to deploy.
- Inside the GritCMS project, click Add Service then Docker Compose
- Source: Connect your Git repository (GitHub / GitLab / Gitea)
- Compose Path:
docker-compose.prod.yml - Build Context:
.(repository root)
The docker-compose.prod.yml file in the repository defines all six services (API, Web, Admin, PostgreSQL, Redis, MinIO) with health checks and proper dependencies.
Step 5: Configure Environment Variables
In Dokploy, navigate to your Docker Compose service and click the Environment tab. Add these variables:
# ─── REQUIRED ────────────────────────────────────────────────
JWT_SECRET=generate-a-64-char-random-string
POSTGRES_USER=grit
POSTGRES_PASSWORD=use-a-strong-password-here
POSTGRES_DB=gritcms
# ─── DOMAINS (update to your actual domains) ────────────────
API_URL=https://api.yourdomain.com
WEB_URL=https://yourdomain.com
ADMIN_URL=https://admin.yourdomain.com
# ─── STORAGE ─────────────────────────────────────────────────
STORAGE_DRIVER=minio
MINIO_ACCESS_KEY=generate-a-key
MINIO_SECRET_KEY=generate-a-secret
MINIO_BUCKET=uploads
# ─── EMAIL (Resend — resend.com) ────────────────────────────
RESEND_API_KEY=re_your_api_key
MAIL_FROM=hello@yourdomain.com
# ─── SECURITY (change defaults!) ────────────────────────────
SENTINEL_ENABLED=true
SENTINEL_USERNAME=admin
SENTINEL_PASSWORD=your-sentinel-password
PULSE_ENABLED=true
PULSE_USERNAME=admin
PULSE_PASSWORD=your-pulse-passwordGenerate secure secrets with:
openssl rand -hex 32Optional Variables
# AI (for AI-powered page builder features)
AI_PROVIDER=claude
AI_API_KEY=sk-ant-your-key
AI_MODEL=claude-sonnet-4-5-20250929
# OAuth (for social login)
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
OAUTH_FRONTEND_URL=https://admin.yourdomain.com
# Cloudflare R2 (alternative to MinIO)
# STORAGE_DRIVER=r2
# R2_ENDPOINT=https://YOUR_ACCOUNT_ID.r2.cloudflarestorage.com
# R2_ACCESS_KEY=your-r2-key
# R2_SECRET_KEY=your-r2-secret
# R2_BUCKET=uploadsStep 6: Configure Domains & SSL
In Dokploy, navigate to your Docker Compose service and click the Domains tab. Add domain mappings for each service:
| Service | Domain | Container Port | HTTPS |
|---|---|---|---|
web | yourdomain.com | 3000 | Yes |
admin | admin.yourdomain.com | 3000 | Yes |
api | api.yourdomain.com | 8080 | Yes |
Dokploy uses Traefik to automatically provision and renew Let's Encrypt SSL certificates. Certificates are issued automatically once DNS propagation is complete.
Step 7: Deploy
Click Deploy in Dokploy. The build process will:
- Pull your code from Git
- Build Docker images for API (Go 1.24), Web (Next.js), and Admin (Next.js)
- Start PostgreSQL, Redis, and MinIO
- Start the API server (auto-migrates the database on first run)
- Start the Web and Admin frontends
The first build takes approximately 5-10 minutes. Subsequent deploys are faster due to Docker layer caching.
Step 8: Initial Setup
- Visit
https://admin.yourdomain.com - Register your admin account (the first user becomes admin)
- The Setup Wizard appears automatically:
- Enter your site name and tagline
- Create your first email list
- Click "Go to Dashboard" -- this saves your settings and creates your Home page with the Creator Home template
- Visit
https://yourdomain.comto see your live site
Updating / Redeploying
Manual Deploy
Dokploy dashboard > your service > Deploy
Auto-deploy via Webhook
- In Dokploy, go to your service > Deployments > copy the webhook URL
- Add it as a webhook in your GitHub/GitLab repo settings
- Every push to
maintriggers an automatic redeploy
Command-line Deploy
git add . && git commit -m "your changes" && git pushIf auto-deploy is enabled, pushing to main triggers the build automatically.
Backups
PostgreSQL
# SSH into your VPS, then:
docker exec gritcms-postgres pg_dump -U grit gritcms > backup_$(date +%Y%m%d).sqlRestore from Backup
docker exec -i gritcms-postgres psql -U grit gritcms < backup_20260226.sqlDokploy Built-in Backups
Dokploy dashboard > Settings > Backups > configure an S3-compatible destination for automatic scheduled backups.
Volumes
All persistent data is stored in Docker named volumes:
postgres-data-- database filesredis-data-- cache and session dataminio-data-- uploaded files and media
Monitoring & Logs
- Dokploy Dashboard: Built-in container logs and resource monitoring for all services
- Pulse: Visit
https://api.yourdomain.com/pulse(login withPULSE_USERNAME/PULSE_PASSWORD) for real-time request monitoring - Sentinel: Visit
https://api.yourdomain.com/sentinel(login withSENTINEL_USERNAME/SENTINEL_PASSWORD) for error tracking - Container Logs: In Dokploy, click any service to view real-time logs
Scaling
| VPS Size | Suitable For |
|---|---|
| 2 GB RAM | Development / testing |
| 4 GB RAM | Small creator site (< 10k monthly visitors) |
| 8 GB RAM | Medium site (10k-100k monthly visitors) |
| 16 GB RAM | Large site (100k+ monthly visitors) |
For high traffic, consider:
- Moving PostgreSQL to a managed database (Supabase, Neon, or AWS RDS)
- Moving Redis to managed Redis (Upstash or ElastiCache)
- Using Cloudflare R2 or Backblaze B2 instead of self-hosted MinIO
- Dokploy supports multi-server clusters for horizontal scaling
Troubleshooting
Build fails with Go version error:
The API Dockerfile uses golang:1.24-alpine. Ensure your go.mod specifies a compatible Go version.
Build fails with "public directory not found":
The web and admin Dockerfiles copy from public/ directories. Make sure apps/web/public/ and apps/admin/public/ directories exist (even if empty, they need a .gitkeep file).
Port already allocated:
The docker-compose.prod.yml uses expose (internal only) instead of ports (host-bound). If you see port conflicts, ensure no other services are using the same ports. Behind Dokploy's Traefik proxy, expose is correct.
API won't start:
- Check logs:
docker logs gritcms-api JWT_SECRET is required-- ensure the environment variable is setconnection refused to postgres-- the API waits for PostgreSQL's health check, but verify theDATABASE_URLis correct
CORS errors:
The CORS_ORIGINS variable is built automatically from WEB_URL and ADMIN_URL in the Compose file. Make sure these match your actual domain URLs.
SSL not working:
- Verify DNS A records point to your VPS IP
- Ensure ports 80 and 443 are open on your VPS firewall
- Check Traefik logs in Dokploy for Let's Encrypt errors