Deploying a FastAPI Backend on DigitalOcean with Dokploy and Docker
I recently moved my backend deployment from “works on my machine” to a production setup on a DigitalOcean VPS using Docker and Dokploy.
The goal was simple:
- clean, repeatable deployments
- low operational cost
- room to scale later
The result is a practical stack that runs the API, PostgreSQL, HTTPS, and backups in a way that is easy to operate and maintain.
Why I chose Dokploy
I wanted something lighter than Kubernetes, but more structured than manually SSH’ing into a server and running everything by hand. Dokploy fit that middle ground.
It gives:
- GitHub-based deployments
- service management
- domain + SSL handling
- backups
- a clean dashboard
Since I already had Dockerized services, Dokploy felt like the natural orchestration layer.
The architecture ended up being:
- a single DigitalOcean VPS
- Dokploy as the deployment platform
- Docker containers for the API and PostgreSQL
- HTTPS handled through Dokploy’s reverse proxy (Traefik)
- GitHub as the source of truth
Step 1: Prepare the application for production
Before deploying anything, I hardened the backend for production.
Main changes:
- slim Docker image
- non-root runtime user
HEALTHCHECK- startup script that runs migrations and then starts Gunicorn with Uvicorn workers
- logging to stdout
TrustedHostMiddleware- secure cookies in production
ALLOWED_HOSTS- reusable outbound HTTP client
- documented
.env.example
These matter because deployment failures are often app-readiness issues, not VPS issues.
Step 2: Push the latest code to GitHub
Dokploy deploys what is pushed, not what exists only locally.
So I made sure all production hardening changes were committed and pushed before server setup.
Step 3: Provision the VPS
I used a 4 GB Ubuntu VPS in Singapore. That gave enough headroom for:
- app container
- PostgreSQL container
- Dokploy
- reverse proxy overhead
After running Dokploy’s installer, I got:
- Docker
- Docker Swarm support
- Traefik for routing/TLS
- Dokploy dashboard (initially on port
3000)
Step 4: Configure DNS
I pointed a subdomain to the VPS with an A record.
This is critical because Let’s Encrypt issuance depends on correct DNS resolution and HTTP reachability.
Step 5: Add PostgreSQL inside Dokploy
Instead of host-level PostgreSQL, I created PostgreSQL as a Dokploy-managed service.
For the internal connection string, I explicitly used:
- internal hostname/network routing
sslmode=disable
That setting only affects DB traffic inside the private network and is separate from public HTTPS.
Step 6: Deploy the API service
In Dokploy, I created a GitHub-connected Docker app and pointed it to:
- repository
- main branch
- Dockerfile build
- internal app port
Then I added environment variables based on .env.example:
- production mode
- database credentials
- JWT secret
- API keys
- allowed hosts
- frontend origin
- base URL
The startup script handled migrations automatically on boot.
Step 7: Enable HTTPS
Once DNS and ports were correct, Traefik handled TLS issuance and routing.
Important detail: the app container still listens on internal HTTP. HTTPS is terminated at Traefik.
Step 8: Verify the deployment
I verified with a health endpoint, not /.
A successful verification means:
- HTTPS works
- domain resolves correctly
- container is running
/healthreturns success
Lessons learned
1) App readiness comes first
Docker alone is not production readiness. Startup behavior, migrations, logging, host validation, and env hygiene matter just as much.
2) Internal DB SSL and external HTTPS are different layers
- DB SSL settings apply to database connectivity
- HTTPS applies to public web traffic
They solve different problems.
3) DNS and ports are common failure points
When TLS fails, usual causes are:
- DNS not propagated
- port 80 blocked
- domain not properly attached
- wrong internal target port
4) 4 GB VPS is a practical baseline
It’s enough for a small production backend stack without premature complexity.
Final architecture
- DigitalOcean VPS: compute
- Dokploy: deployment manager
- Docker: runtime
- PostgreSQL container: database
- Traefik: SSL + routing
- GitHub: source and deployment trigger
- Health endpoint: operational check
Closing thoughts
This setup feels production-grade without being heavy. For a solo dev or small team, it is a practical path: harden the app, containerize it, deploy with Dokploy, and keep operations simple.
Related on this site
- Deploying Node instead? Compare with Deploying an Express.js App with PostgreSQL on a VPS.
- Planning the backend's data model? Read the Complete Guide to Multi-Tenant Architecture.
- See a Python/Django + Docker + PostgreSQL stack in production in the Multi-Agent Research Assistant project.