The Problem
GitHub Actions and Vercel abstract away everything. Push code, see green checkmark, deployed. But what happens underneath? To actually understand CI/CD — security isolation, infrastructure provisioning, real-time observability — you have to build one from raw parts.
What I Built
A deployment engine that clones a GitHub repo on push, builds it in an isolated Docker container, runs tests, and deploys the output to a subdomain — streaming build logs to the browser in real time via WebSocket. Framework detection auto-generates Dockerfiles for projects that don't have one.
The Decisions
Docker container isolation
Every build runs in its own container. Non-negotiable when you're executing arbitrary user code on your own server. One compromised build cannot touch another.
HMAC-SHA256 webhook verification
Only real GitHub push events trigger builds. Without this, anyone who knows your endpoint URL could trigger builds — or worse, arbitrary code execution.
AES-256-GCM for stored secrets
User environment variables are encrypted at rest. No plaintext tokens in the database. This is the difference between "it works" and "it's safe."
WebSocket build streaming
Real-time log output in the browser. Chose WebSocket over SSE for bidirectional capability — future: cancel builds mid-stream.
Self-hosted on Hetzner VPS
Nginx as reverse proxy. Wildcard SSL via Let's Encrypt for dynamic subdomains. PostgreSQL for state. PM2 for process management. Full stack, full control.
"Three weeks before I started Shipyard, I had never used Docker, PostgreSQL, Drizzle, a VPS, Nginx, or Let's Encrypt. I learned all of them because the project demanded it — the way engineering tools should be learned."
What It Can't Do Yet
- No multi-user auth (single-owner only)
- No build caching (every build starts clean)
- No custom domain mapping for deployed projects (subdomains only)
- No rollback mechanism (must re-deploy from a previous commit)