Paste a GitHub repo link and get a live website. Shipyard is a self-hosted deploy platform, a small version of Vercel or Netlify built from scratch. It builds a site inside an isolated Docker container, streams the build logs live, and serves the result at https://<project>.<domain>.
It supports static sites and single-page apps: Vite, Create React App, plain HTML and Next.js static export.
Status: Milestone 1 of 6 is complete: GitHub sign-in and project creation work end to end, with 50 passing tests. Builds, live logs and serving are next (see the roadmap).
- Sign in with GitHub on the dashboard.
- Add a project: paste a repo URL and pick a name. The name becomes the site's subdomain.
- Deploy: the API puts a build job on a queue instead of building right away.
- Build in isolation: a worker clones the repo and builds it inside a throwaway Docker container with CPU, memory and time limits.
- Watch it live: every build log line is streamed to the browser as it happens.
- Go live: the built files are uploaded to object storage, and the project's "active deployment" pointer switches to the new build in one atomic update.
- Serve: the router maps
my-site.<domain>to that deployment's files.
flowchart LR
B[Browser] --> W[Dashboard<br/>Next.js]
W --> A[API<br/>Fastify]
A --> P[(Postgres)]
A -- enqueue job --> R[(Redis<br/>BullMQ + pub/sub)]
R -- next job --> K[Worker]
K -- runs --> C[[Build container<br/>1 CPU · 1 GB · 10 min]]
K -- upload --> S[(Object storage<br/>MinIO / S3)]
K -- log lines --> R
R -- live logs --> A
V[Site visitor] --> X[Router] --> S
More detail: docs/architecture.md · design tradeoffs: docs/decisions.md
| Layer | Technology |
|---|---|
| Language | TypeScript (strict) everywhere |
| Monorepo | pnpm workspaces + Turborepo |
| Dashboard | Next.js 16 (App Router), Tailwind CSS, shadcn/ui-style components |
| API | Fastify 5, zod validation |
| Database | PostgreSQL + Prisma 7 |
| Auth | Auth.js (GitHub OAuth) |
| Queue | BullMQ on Redis |
| Builds | Worker using dockerode to run Docker containers |
| Storage | MinIO locally, S3 / Cloudflare R2 in production |
| Testing | Vitest (unit + API integration), Playwright (planned) |
| CI | GitHub Actions: lint, typecheck, test |
- Untrusted code stays in a box. Builds run in throwaway containers as a non-root user, with resource limits, no Docker socket and no secrets.
- Only the API touches the database. The dashboard calls the API, which decrypts the same Auth.js session cookie using a shared secret.
- One validation source. The same zod schemas check the form in the browser and the request in the API.
- No shell injection. Build commands are rejected if they contain shell operators (
&&,|,;,$), and they run as argument arrays. - Instant rollback. Going live is a single-row pointer switch, so rolling back is the same switch pointed at an older build.
- Backpressure. A burst of deploys waits in the queue instead of overloading the server.
apps/
web/ Next.js dashboard
api/ Fastify REST API (+ WebSocket logs in M3)
worker/ Docker build runner (M2)
router/ Subdomain reverse proxy (M4)
packages/
db/ Prisma schema, migrations, client
shared/ zod schemas, types, constants
config/ tsconfig, ESLint, Prettier presets
docs/ architecture and decision log
Prerequisites: Node 22+, pnpm 10, Docker Desktop.
-
Create a GitHub OAuth app at https://github.com/settings/developers:
- Homepage URL:
http://localhost:3000 - Callback / redirect URL:
http://localhost:3000/api/auth/callback/github
- Homepage URL:
-
Configure environment: copy
.env.exampleto.envand fill inAUTH_SECRET,AUTH_GITHUB_IDandAUTH_GITHUB_SECRET..envis gitignored; never commit it. -
Start:
pnpm install docker compose up -d # Postgres (port 5433), Redis, MinIO pnpm db:migrate # create the database tables pnpm dev # dashboard on :3000, API on :4000
-
Open http://localhost:3000 and sign in.
| Command | What it does |
|---|---|
pnpm dev |
Run all apps in watch mode |
pnpm test |
Unit tests + API integration tests (needs Docker running) |
pnpm lint / pnpm typecheck |
ESLint and TypeScript checks |
pnpm db:migrate |
Apply database migrations |
- M1 Skeleton: monorepo, Docker Compose, Prisma schema, GitHub login, create project
- M2 Builds: queue + worker + Docker build, status updates
- M3 Live logs: Redis Pub/Sub → WebSocket → log viewer
- M4 Serving: upload to MinIO, router serves
<slug>.localhost - M5 Polish: deployment history, one-click rollback, build cancellation, framework auto-detect
- M6 Prove it: k6 load test numbers, GitHub webhook auto-deploy
Demo video and load-test numbers will be added as the milestones land.