How to install, configure, and put Shipyard behind Caddy (preferred) or nginx.
| Piece | Default |
|---|---|
| Control plane API | :8080 |
| Operator UI (dev) | :5173 (Vite) or served by reverse proxy |
| PostgreSQL | :5432 |
| Public URL | https://shipyard.example.com |
Chain: source → pipeline → job → artifact/package/image → release → deployment
From a Linux host with Docker (or rootless Docker):
# interactive — prompts for domain + proxy
./scripts/install.sh
# non-interactive example (Caddy)
DOMAIN=shipyard.example.com \
EMAIL=ops@example.com \
PROXY=caddy \
./scripts/install.sh --yesThe script will:
- Check Docker / Compose
- Write
.envfrom.env.example(secrets, public URL) - Start Postgres +
shipyard-servervia Compose - Install Bun deps and optionally run the UI
- Drop a Caddyfile or nginx site config under
deploy/ - Print next steps (first user, runners, Discord, forge webhooks)
cp .env.example .env
# edit SHIPYARD_PUBLIC_URL, SHIPYARD_WEBHOOK_SECRET, SHIPYARD_SECRETS_KEY, …
export PATH="$HOME/bin:$PATH" # rootless Docker on Arch
export DOCKER_HOST=unix:///run/user/$UID/docker.sock
docker compose -f deploy/compose/compose.yml up --build -d
curl -sf http://127.0.0.1:8080/healthzUI (development):
bun install
SHIPYARD_PUBLIC_URL=http://127.0.0.1:5173 bun run devOpen http://127.0.0.1:5173 — first visit can register when the user table is empty.
Caddy auto-HTTPS when DNS points at the host.
# after install.sh, or copy manually:
sudo cp deploy/caddy/Caddyfile /etc/caddy/Caddyfile
# set {$SHIPYARD_DOMAIN} or edit the site address
sudo systemctl reload caddyExample deploy/caddy/Caddyfile:
- Terminates TLS
- Serves
/→ Vite preview or static UI build (or proxies to:5173in lab) - Proxies
/api,/v2,/auth,/repository,/healthz,/readyz→:8080
sudo cp deploy/nginx/shipyard.conf /etc/nginx/sites-available/shipyard
sudo ln -s /etc/nginx/sites-available/shipyard /etc/nginx/sites-enabled/shipyard
# set server_name + TLS paths
sudo nginx -t && sudo systemctl reload nginxUse Certbot or your own certificates. Prefer Caddy when you can.
- Open the UI → Create first account (allowed when no users exist, or
SHIPYARD_ALLOW_REGISTER=true) - Projects → create org + project
- Pipelines → save a pipeline → Run
- Runners → Generate install command → copy the
curl | bashone-liner onto a host (Docker or Go). Same idea as Pterodactyl Wings. - Settings → Integrations → connect GitHub / Forgejo / GitLab / …
- Point forge webhooks at
https://YOUR_DOMAIN/api/v1/webhooks/{provider}?connection_id=… - Settings → Notifications → Discord webhook or bot (Sentry-style embeds)
| Variable | Purpose |
|---|---|
SHIPYARD_DATABASE_URL |
Postgres DSN |
SHIPYARD_PUBLIC_URL |
Links in bot comments + Discord embeds |
SHIPYARD_API_URL |
API origin embedded in runner curl | bash install scripts |
SHIPYARD_WEBHOOK_SECRET |
Global webhook HMAC / shared secret fallback |
SHIPYARD_SECRETS_KEY |
Base64 32-byte key for encrypted secrets |
SHIPYARD_ALLOW_REGISTER |
Open registration (otherwise first-user only) |
SHIPYARD_OIDC_* |
Login presets (GitHub, GitLab, Forgejo, Gitea, Entra, Discord, Halo, generic) |
See .env.example for the full list.
- Put Postgres on a volume; back it up
- Set strong
SHIPYARD_SECRETS_KEYandSHIPYARD_WEBHOOK_SECRET - Set
SHIPYARD_ALLOW_REGISTER=falseafter bootstrap - Prefer filesystem or S3 for blobs (
SHIPYARD_STORAGE_BACKEND) - Run at least one runner with labels matching your jobs
- Keep
SHIPYARD_PUBLIC_URLon the HTTPS origin users hit
./scripts/smoke.sh| Symptom | Check |
|---|---|
| UI can’t reach API | Proxy /api → :8080; Vite proxy in apps/web/vite.config.ts for local |
| Webhooks 401 | Connection webhook secret vs forge signature headers |
| Discord silent | Send test in Settings; confirm webhook URL / bot intents |
| Runner idle | Registration token, labels, server URL reachable from runner |
| Migrations fail | Postgres healthy; SHIPYARD_MIGRATIONS_DIR mounted in Compose |