Deployher (deployher.com) is an open-source deployment platform for . The public site runs the same codebase you can run on your own VPS or in your cloud account. It uses bun, drizzle, postgres, redis, garage, and docker. With Deployher, you can connect GitHub repos, trigger builds, and serve mobile apps via subdomain or path.
Deployments are processed by a dedicated deployment worker service (deployment-worker), not by app-api. The worker has Docker socket access and the full build toolchain, while app-api stays focused on API + dashboard static assets and related HTTP traffic.
The worker talks to the host Docker daemon through dockerode over /var/run/docker.sock.
The queue uses Redis Streams consumer groups, so multiple worker replicas can process deployments concurrently.
Note
Only the deployment worker and preview-runner mount the Docker socket. The app never talks to Docker; build cancellation is signaled over Redis and workers remove labeled build containers.
The Compose stack includes:
edge: Caddy front door on port 3000 (routes to API, dashboard, marketing, previews)app-api: HTTP API + prebuilt dashboard SPA (SKIP_CLIENT_BUILD=1in Compose; assets baked into the image)marketing: static Astro landing site (nginx image)preview-runner: loadsruntime-image.tarfrom S3 (or registry), runs bounded preview containers; app usesRUNNER_URLat/preview/<deploymentId>/…deployment-worker: Redis consumer that runs builds via Dockerdeployher-node-build-image:latest: Node build image withpnpmpre-activated via Corepackdeployher-bun-build-image:latest: Bun build image with Python and native build deps for packages likecanvas
Scale workers:
docker compose up -d --scale deployment-worker=4Each deployment emits a container image tarball (runtime-image.tar, Docker save format) under the deployment artifact prefix in object storage. Static deployments also emit a preview-manifest.json so previews can resolve assets without repeated object-store existence checks. The image tarball is compatible with docker load, containerd, and other runtimes.
- Install Docker (and Docker Compose if not bundled).
- Clone,
cp .env.example .env, and fill GitHub OAuth,BETTER_AUTH_SECRET, and Nexus (NEXUS_*). Defaults and most tunables live inconfig/default.toml; optional overrides inconfig/local.toml— see docs/SETUP.md. - Run
bun run deployher startonce (bootstrap: infra, migrations + seed viaoven/bunin Docker, app + workers). That needs Bun for the CLI process. For a standalone infra CLI with no Bun at runtime, runbun run build:clithen./dist/deployher-cli start. PutdeployheronPATHwithbun link --global(runscli/via Bun) orbun run cli:linkafter a compile (symlinksdist/deployher-clito~/.local/bin/deployher). - After that, choose how you run day-to-day:
Full stack in Docker only (no Bun on the host for day-to-day):
docker compose up -d --buildApp (via deployher-edge): http://localhost:3000. Migrations run in app-api on startup; seed is not re-run by Compose — use deployher seed (Docker) or bun run seed on the host when using hot-reload dev below.
Infra in Docker, app + worker on the host (hot reload, requires Bun):
docker compose stop edge app-api marketing deployment-worker
# terminal 1
bun run dev
# terminal 2
bun run start:worker
# terminal 3 (server previews): bun run start:preview-runner — needs Docker + same S3 env as the app; set RUNNER_DOCKER_NETWORK to your compose default network (e.g. deployher_default) if the runner runs in a containerDocker app (via edge on http://localhost:3000) proxies the Bun API and dashboard SPA (dist/client). For local HMR, run bun run dev (API) and bun run dev:vite (default http://localhost:5173); Vite proxies /api, /d, and /preview to the Bun server (VITE_DEV_API_URL overrides the proxy target). Marketing: bun run dev:marketing (Astro, default port 4321) or the marketing compose service (static nginx). If OrbStack or another service already owns 3000, docker compose stop edge app-api marketing deployment-worker frees the Compose edge port; it does not stop unrelated listeners on the host. Health: GET or POST /health (JSON or HTML) when path routing or a single host reaches app-api; with split-domain routing (DEPLOYHER_EDGE_USE_PATH_ROUTING=0), probe the API hostname (e.g. GET https://api.example.com/api/health or curl to edge with Host: <your API hostname>). Deployment previews: subdomain <id>.<DEV_DOMAIN>:<PORT> or path /d/<id>/.... Full workflows: docs/SETUP.md.
Server previews are enabled when RUNNER_URL points at the preview-runner. Set RUNNER_PREVIEW_ENABLED=0 to turn them off without removing RUNNER_URL. Match S3 (and optional registry) credentials on the runner. Optional RUNNER_SHARED_SECRET is sent as x-deployher-runner-secret.
Ready-to-use sample repos live in /examples:
examples/node-npm-staticexamples/node-pnpm-staticexamples/node-bun-staticexamples/bun-server-apiexamples/bun-server-clientexamples/node-yarn-staticexamples/python-mkdocs-pipexamples/python-deployher-pip
See examples/README.md for usage.
| Layer | Technology |
|---|---|
| Runtime | bun |
| HTTP / UI | Bun.serve front door; Vite dashboard SPA (dist/client); Astro marketing site (apps/marketing) |
| Auth | Better Auth (session, GitHub OAuth) |
| Database | postgres with drizzle |
| Queue | redis (Streams + consumer groups) |
| Storage | garage (S3-compatible); build artifacts and preview assets |
| Infra | docker and docker-compose |
| Path | Purpose |
|---|---|
src/index.ts |
HTTP server entrypoint |
src/router.ts |
Route table and request handling |
src/routes/ |
API handlers and Bun front-door page/API contracts (projects, deployments, GitHub, account) |
apps/marketing/ |
Astro static site for apex landing and /why (nginx marketing image) |
apps/marketing/public/ |
Marketing-only static assets (e.g. hero image) copied into the Astro output |
public/web/ |
Dashboard-only static assets for the Vite client build |
src/workers/buildWorker.ts |
Core build-job processor (clone, install, build, upload) |
src/workers/runBuildWorker.ts |
Standalone build worker entrypoint |
src/ui/ |
React pages and client-side entrypoints |
src/db/ |
Drizzle schema and DB client |
auth.ts |
Better Auth config |
drizzle/ |
Migrations |
cli/ |
Dev infra: Postgres, Redis, Garage, Nexus; migrate/seed via oven/bun in Docker; app + workers. Run with bun run deployher, ./dist/deployher-cli after bun run build:cli, or deployher on PATH. |
config/default.toml |
Committed app defaults (Compose handles in-network service wiring separately) |
docker-compose.yml |
Edge, app-api, marketing, preview-runner, deployment-worker, builder images, Postgres, Redis, Garage, Nexus |
Dockerfile |
Multi-stage build for the app image |
docker/build-worker.Dockerfile |
Docker image for the standalone deployment worker |
Bun can compile the server into one binary, but this path is optional. Docker + Bun runtime is the supported production path (API + static dist/client + optional marketing image).
Current platform:
bun run build:exeOutput: dist/deployher
Linux (e.g. for a server):
bun run build:exe:linux-x64
bun run build:exe:linux-arm64Run the binary:
chmod +x dist/deployher
SKIP_CLIENT_BUILD=1 ./dist/deployherInfra-only CLI (Compose / migrate / seed helpers, no full app bundle): bun run build:cli writes dist/deployher-cli (separate from build:exe, which produces dist/deployher for the full server). Use bun run cli:link to symlink dist/deployher-cli into ~/.local/bin/deployher; it prompts before editing ~/.zshrc to add PATH. bun run cli:link -- --yes skips the prompt (e.g. CI); CLI_LINK_NO_ZSHRC=1 only creates the symlink.
Note
Build workers run via bun run start:worker (or the deployment-worker Compose service). src/workers/buildWorker.ts contains the shared worker loop used by that entrypoint.
| Surface | In this repo | Typical production |
|---|---|---|
| Dashboard | Vite app under src/ui/ → bun run build:web → dist/client |
Baked into app-api image; edge routes your dash host to app-api |
| Marketing | apps/marketing (Astro) |
marketing compose service; edge routes apex / landing hostnames |
| API + auth | src/ (Bun server) |
Same app-api container |
| Hand-written docs | docs/*.md (this tree) |
Not a separate Compose service; publish however you like (e.g. GitHub wiki, static site, or a path on marketing) |
Upgrades on your server are usually: git pull (or reset to a release tag) then docker compose up -d --build from the directory that holds docker-compose.yml and .env. Optional GitHub Actions for test-then-SSH-deploy: see docs/SETUP.md#continuous-deployment-github-actions and .github/workflows/deploy.yml (adjust the remote cd path and smoke-build VITE_PUBLIC_* URLs to match your domain).
- docs/SETUP.md: Prerequisites, bootstrap,
deployherCLI (migrate/seed viaoven/bunin Docker, no Bun on host), both dev workflows, env vars, ports, CLI reference, health endpoints, preview URL formats, build pipeline and workers, database and migrations, npm scripts, production deployment, optional CI/CD, troubleshooting. - docs/DEPLOYMENT.md: Monorepo workspace/app roots, runtime image modes, Dockerfile-first server deploys, Nexus-aware Docker build args, and security notes.
- docs/SPLIT_DOMAIN.md: Production split-domain setup — apex marketing,
dash.SPA,api.auth/API, DNS (e.g. Spaceship),.env, GitHub OAuth callback,VITE_PUBLIC_*rebuild, TLS proxy headers.
