Phase 3 — deployments
Update: completion work adds account/team controls, owned DNS, private GitHub, Git automation and scheduled cold backups. Where earlier limits below differ, that guide describes the current implementation and required migrations.
Applications now support public GitHub repositories with a committed Dockerfile, or public docker.io / ghcr.io images. The dashboard queues a release; the assigned agent claims it once, resolves repository refs to immutable commits, builds through rootless BuildKit, loads the image, starts a constrained candidate, checks HTTP health, and switches a loopback Caddy route. Success is recorded only after the proxy returns the expected release header. Rollback reuses a previous successful immutable image and its encrypted environment. Interrupted work becomes unknown, never assumed successful.
Upgrade the control plane
Back up PostgreSQL and the encryption key. With a migration administrator, apply infrastructure/migrations/004_deployments.sql once, then provision the worker login using infrastructure/postgres/005_worker_user.sh with WORKER_DB_PASSWORD in its environment. The shell script expects the PostgreSQL container's psql environment; run it there. Existing volumes do not rerun initialization. Do not reset a database containing data.
Set a new, persistent SECRET_ENCRYPTION_KEY in the API environment: 32 cryptographically random bytes encoded as 64 hexadecimal characters. Generate it with node -e "console.log(require('crypto').randomBytes(32).toString('hex'))". Keep it in a secret store and back it up separately. Changing it without re-encryption makes stored release environments and tunnel credentials unreadable. AES-GCM binds ciphertext to the workspace and application/release owner. Values are never returned by dashboard APIs; only key names are shown. Database administrators remain trusted.
Set a distinct URL-safe WORKER_DB_PASSWORD; start the API and services/worker with PostgreSQL and Redis. The Asynq worker expires undispatched releases, marks stalled work unknown, ages health observations, and prunes seven-day logs. It has a restricted database role and cannot execute customer code. Compose includes the worker; Go services read process environment directly, not dotenv files.
Enable a deployment server
Use the Phase 2 Ubuntu prerequisites and checksum-verified agent installer. Install an independently verified Caddy binary at /usr/local/bin/caddy, owned by root and not group/world writable. Run the installer with both --enable-docker and --enable-deployments, plus the existing binary/API/checksum arguments. Existing pairing state is retained; the installer restarts the upgraded service. Docker socket access is a privileged opt-in on your own server.
Image deployments need Docker and the local Caddy service. Repository builds additionally need a separate, unprivileged BuildKit service, never the agent's Docker socket as a build backend:
- Create a dedicated
ownserva-buildaccount with a writable home at/var/lib/ownserva-buildkit, valid subordinate UID/GID allocations, and workingnewuidmap/newgidmap. Install verified BuildKit (buildkitd,buildctl), RootlessKit, slirp4netns, and a supported snapshotter. Install the trusted executables under/usr/local/binas root-owned, non-writable files. The agent validatesbuildctlbefore executing it. - Review the rootless BuildKit prerequisites, including Ubuntu's user-namespace/AppArmor requirements. Do not disable process sandboxing or enable insecure entitlements. RootlessKit must use its isolated network with host loopback disabled. Do not mount the Docker socket, host secrets, or agent state into the builder.
- Install the template ownserva-buildkit.service under that exact name. It enforces 2 GiB RAM, no swap, two CPUs, and 1,024 tasks. Give
ownservasupplementary membership inownserva-buildso it can traverse the private runtime directory and access the BuildKit socket; restart the agent after group changes. Rootless namespace mappings must permit the requested socket group; confirm access asownservabefore enabling builds. - Put the builder state and Docker storage on monitored, quota-limited filesystems and configure BuildKit garbage collection. The agent enforces source/archive/output/time limits but does not enforce a hard builder cache or Docker image disk quota. The unit is a starting template requiring acceptance on your Ubuntu release.
- Confirm server telemetry reports
deployments_enabled,proxy_ready, and, for repository builds,builder_ready. The agent verifies the Unix peer is a different non-root user and checks effective cgroup-v2 limits insideownserva-buildkit.service.
Builds receive no runtime environment or host Docker credentials. Source download rejects redirects, archive escapes, symlinks, special files and excessive sizes; only public GitHub sources are supported. Containers run as UID/GID 10001, drop all capabilities, use no-new-privileges, have memory/CPU/PID limits, rotated logs and a temporary /tmp. They publish only an ephemeral 127.0.0.1 port. Images must support non-root execution. Persistent storage is not provisioned; container files are not a backup.
The local proxy listens on 127.0.0.1:8088, with a private Unix admin socket. <application UUID>.localhost:8088 is a server-local hostname, not a public endpoint on your laptop. Phase 4 provides outbound public tunnels. No inbound customer firewall changes are made.
Verify on Ubuntu
Deploy a public image compatible with UID 10001 and its configured port. Then commit examples/hello as the root of a public test repository and deploy it with Dockerfile Dockerfile, port 8080 and health path /. Confirm build logs, the resolved commit/image, current release header and local HTTP response. Redeploy, then roll back and verify the old immutable image returns. Use an unhealthy image to confirm the previous route survives. Restart the agent during a test build and verify it reports unknown instead of rerunning it. Confirm another workspace cannot read the app and a viewer cannot mutate it or read logs.
Logs are bounded and best effort. Literal runtime secret values are redacted in the agent and API, but transformed, split, or application-derived secrets can still appear; only owner/admin/developer roles can read logs. Runtime logs may overlap after reconnects. Do not deliberately print secrets. Build progress retries reuse an immutable batch identity.
Private GitHub App access, buildpacks/framework auto-detection, ZIP/Compose uploads, build secrets, volumes, deletion/GC UI, and per-application stop controls are not included. Existing server controls operate on managed containers. Inspect unknown outcomes on the server before retrying; a lost response is not proof of failure. This workspace has no Docker daemon or Ubuntu runtime, so these acceptance steps have not been executed here.