Shipyard is an MVP deployment orchestration platform for static sites. It uses project-level configuration (repo + build settings), runs containerized builds, uploads artifacts to MinIO (S3-compatible), and serves deployments by ID.
- Project APIs (
POST /projects,GET /projects,GET /projects/:id) - Deployment trigger API (
POST /projects/:id/deployments) - FIFO worker with retry logic
- Docker-based build execution
- MinIO artifact storage
- Deployment status endpoint
- Deployment logs endpoint
- NGINX reverse proxy
- Artifact checksum persistence (
artifact_checksum) - GitHub Webhooks for automated deployments
- Deployment actions: retry, cancel, and redeploy
- Go (Echo v5)
- PostgreSQL
- MinIO
- Docker / Docker Compose
- NGINX
- API receives project deployment trigger and inserts a
QUEUEDdeployment row linked toproject_id. - Worker polls queue, transitions to
BUILDING, and runs the build in Docker. - Built files are uploaded to MinIO bucket
deploymentsunder<deployment_id>/.... - Worker stores artifact checksum and marks deployment as
READY. - Static files are served via API/NGINX route using deployment ID.
- A user creates a project with
repo_url,build_preset,output_dir, anddefault_branch. - A deployment is triggered for that project via
POST /projects/:id/deployments. - Shipyard inserts a
QUEUEDdeployment row linked to the project. - The worker clones the configured repo/branch, runs the preset build in Docker, and uploads artifact output to MinIO.
- Shipyard records deployment logs, lifecycle metadata, and artifact checksum.
- If the build succeeds, deployment becomes
READYand is served at/<deployment_id>. - Users inspect status with
GET /deployments/:id, list history withGET /deployments, and read logs withGET /logs/:id. - Users can retry failed deployments, cancel queued/building deployments, or redeploy from any completed/failed deployment.
QUEUED- Deployment is waiting in the queueBUILDING- Worker is currently building the deploymentREADY- Deployment was built successfully and is being servedFAILED- Deployment failed (can be retried)CANCELLED- Deployment was cancelled by user
- Docker
- Docker Compose
docker compose up --buildThis starts:
postgresonlocalhost:5432minioAPI onlocalhost:9000minioconsole onlocalhost:9001apionlocalhost:8082workernginxonlocalhost:8001
Token auth is required for protected endpoints (X-API-Key or Authorization: Bearer <token>).
Database migrations are applied automatically on startup by both api and worker.
Migration files live in migrations/ and are tracked in the schema_migrations table.
For CI or local verification, starting the app is enough to apply pending migrations:
docker compose up --buildcurl http://localhost:8082/healthzbash scripts/smoke_test.shOptional overrides:
API_URLPROXY_URLAPI_KEYPROJECT_NAMEHEALTH_TIMEOUT_SECONDSDEPLOY_TIMEOUT_SECONDSPOLL_INTERVAL_SECONDS
curl -X POST http://localhost:8082/projects \
-H "X-API-Key: <token>" \
-H "Content-Type: application/json" \
-d '{
"name":"my-site",
"repo_url":"https://github.com/<owner>/<repo>",
"build_preset":"vite",
"output_dir":"dist",
"default_branch":"main"
}'Response includes project_id.
name: required. Unique per user.repo_url: required. Must be a validhttpsURL. Whitespace is rejected.build_preset: required. One of the supported presets listed below.output_dir: required for most presets. Must be a relative path inside the repo (no leading/, no..traversal). If omitted forstatic-copy, the repo contents are copied to the artifact workspace.default_branch: optional. Defaults tomain.
static-copy: copies files without building (useful for already-built/static repos)npm: runsnpm ci && npm run buildvite: runsnpm ci && npm run buildnext-export: runsnpm ci && npm run build && npm run export
For safety, Shipyard currently only allows cloning from:
github.com
curl -X POST http://localhost:8082/projects/<project_id>/deployments \
-H "X-API-Key: <token>"Response includes deployment_id.
curl http://localhost:8082/deployments/<deployment_id> \
-H "X-API-Key: <token>"Response now includes lifecycle metadata:
started_atfinished_aterror_messagebuild_duration_seconds
curl "http://localhost:8082/deployments?limit=20&offset=0" \
-H "X-API-Key: <token>"curl http://localhost:8082/logs/<deployment_id> \
-H "X-API-Key: <token>"Retries a failed deployment by resetting it back to the queue with attempt count reset to 0.
curl -X POST http://localhost:8082/deployments/<deployment_id>/retry \
-H "X-API-Key: <token>"Response includes deployment_id. Only failed deployments can be retried.
Cancels a queued or building deployment.
curl -X POST http://localhost:8082/deployments/<deployment_id>/cancel \
-H "X-API-Key: <token>"Response includes deployment_id. Only queued or building deployments can be cancelled.
Creates a new deployment based on an existing deployment's configuration.
curl -X POST http://localhost:8082/deployments/<deployment_id>/redeploy \
-H "X-API-Key: <token>"Response includes the new deployment_id. Can be used on ready, failed, or cancelled deployments.
curl http://localhost:8082/projects \
-H "X-API-Key: <token>"- Via API:
http://localhost:8082/<deployment_id>http://localhost:8082/<deployment_id>/assets/app.js
- Via NGINX proxy:
http://localhost:8001/<deployment_id>
curl -X POST http://localhost:8082/tokens \
-H "X-API-Key: <token>" \
-H "Content-Type: application/json" \
-d '{"name":"my-token","expires_at":"2025-12-31T23:59:59Z"}'Response includes the raw token (shown only once).
curl http://localhost:8082/tokens \
-H "X-API-Key: <token>"curl -X DELETE http://localhost:8082/tokens/<token_id> \
-H "X-API-Key: <token>"Shipyard supports GitHub webhooks for automated deployments. Each project can have its own webhook URL.
curl -X POST http://localhost:8082/projects/<project_id>/webhook \
-H "X-API-Key: <token>"Response includes webhook_url (e.g., http://localhost:8082/webhooks/github?project_id=<project_id>&secret=<secret>).
curl http://localhost:8082/projects/<project_id>/webhook \
-H "X-API-Key: <token>"Returns the webhook URL and secret. The secret is used to verify webhook payloads.
In your GitHub repository settings:
- Go to Settings > Webhooks > Add webhook
- Payload URL: Use the webhook URL from above
- Content type:
application/json - Events: Select "Just the push event" (or customize as needed)
- Secret: Enter the secret from the webhook response
- Add webhook
When a push event occurs, GitHub sends a webhook to Shipyard, which automatically triggers a new deployment for the configured branch.
- Console:
http://localhost:9001 - Username:
shipyard_minio - Password:
shipyard_minio_change_me
Artifacts are stored in bucket deployments.
-
POST /projects,GET /projects,GET /projects/:id,POST /projects/:id/deployments,GET /deployments,GET /deployments/:id, andGET /logs/:idrequire token auth:- Header
X-API-Key: <key>orAuthorization: Bearer <key> - Tokens are stored hashed in
api_tokens - Raw tokens are never stored in DB
- Header
-
nginx.confproxies tohttp://api:8082inside Docker Compose network. If you run NGINX outside Compose, adjust upstream accordingly. -
Worker runs
docker runinternally for builds. If worker is containerized, ensure it can access Docker:- mount Docker socket:
/var/run/docker.sock:/var/run/docker.sock - have Docker CLI available in worker image
- mount Docker socket:
Without this, build jobs may fail inside the worker container.
# Rebuild and restart
docker compose up --build
# Inspect applied migrations
docker compose exec postgres psql -U shipyard -d shipyard -c "SELECT * FROM schema_migrations ORDER BY applied_at;"
# Follow API logs
docker compose logs -f api
# Follow worker logs
docker compose logs -f worker
# Stop all
docker compose down