projects / 01
dploy
self-hosted PaaS
A self-hosted PaaS in Go. Deploys start from the dashboard: paste a repo URL, or hit redeploy to ship the latest commit. Cloning, framework detection (Next.js, Vite, Go, Bun, Node, Python, static HTML), Docker builds and S3 uploads run on asynchronous Redis/Asynq workers, so the API never blocks. Traffic goes through dploy's own zero-downtime reverse proxy, which load-balances dynamically to the least-busy replica and routes platform subdomains and custom domains. An autoscaler grows and shrinks replicas from live connection counts, and build logs stream to the dashboard over WebSockets.
how it works
- 01
Trigger
Paste a Git repo URL in the dashboard, or press redeploy on an existing project. The Go API stores the deployment and enqueues a task on Redis through Asynq; it never builds anything itself.
- 02
Build
A separate worker process picks up the task, clones the repo, detects the framework (Next.js, Vite, Go, Bun, Node, Python or static HTML) and writes a Dockerfile unless the repo ships one. Static sites are uploaded to S3 instead of becoming containers.
- 03
Run and cut over
The worker starts the new replica through the Docker Engine API and warms the proxy's route cache, so new requests go to it immediately. Old replicas are retired two seconds later.
- 04
Route
Caddy terminates TLS, issuing certificates on demand once the API confirms the domain, and hands every request to dploy's proxy. The proxy resolves the Host header to a platform subdomain or a custom domain, then picks the replica with the fewest active connections.
- 05
Keep it healthy
Inside the API process a loop runs every second: it restarts crashed containers with exponential backoff and resizes each app between 1 and 5 replicas based on live connection counts.
- 06
Watch
The worker publishes build output to a Redis channel per deployment; the API subscribes and streams it to the dashboard over a WebSocket.
design decisions
Its own reverse proxy
A static upstream list can't follow replicas that the autoscaler and rollouts add and remove every few seconds. Routing per request against live connection counts lets traffic shift as soon as a replica appears.
API and worker as separate processes
Cloning and Docker builds take minutes and lots of CPU. Running them behind a queue keeps the API responsive and lets builds fail or retry without taking the control plane down.
Docker instead of Kubernetes
Every moving part (Go, Docker, Redis, Postgres, S3, Caddy) runs on a single host, and the orchestration logic stays small enough to read.
highlights
- Custom reverse proxy tracks live connections per replica and routes to the least-loaded one
- Autoscaler sizes each app between 1 and 5 replicas from total connections, with up/down cooldowns
- A reconciler restarts crashed containers with exponential backoff (5s doubling, capped at 5min)
- New deploys take traffic before old replicas are retired; rollbacks reuse the previous image
known limitations
- Cutover waits a fixed two seconds before removing old replicas instead of waiting for their connections to reach zero.
- Connection counts live in memory in the API process, so the proxy runs as a single instance.
- Autoscaling is capped at 5 replicas per app.
stack
- go
- docker
- redis
- asynq
- postgres
- s3
- caddy
- next.js