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.
FastAPI + Dokploy + Docker deployment architecture
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.
Deployment request flow from browser to FastAPI container
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.
