Deploy to production
Architecture
Section titled “Architecture”devices / CLI / dashboard ──HTTPS──▶ Caddy ──▶ Patchkite server ──▶ PostgreSQL │devices (package downloads) ◀── presigned URL ── S3 / R2- Server (
ghcr.io/patchkite/server): API, the dashboard at/web/, and the SDK endpoints at/v1/public/*. It is stateless and runs database migrations automatically on start. - PostgreSQL: users, apps, deployments, release metadata, and metrics.
- S3-compatible storage: packages, manifests, and diffs. AWS S3 and Cloudflare R2 both work; R2 has no egress fees, which suits update downloads.
- Caddy: automatic HTTPS with Let’s Encrypt.
Requirements
Section titled “Requirements”- A Linux host with Docker and Docker Compose, with ports 80 and 443 open.
- A domain such as
patchkite.example.com, with DNS A/AAAA records pointing to the host. - An S3 or R2 bucket and an access key allowed to
GetObject,PutObject,DeleteObject, andListBucket. - At least 1 vCPU and 1 GB of RAM. Uploads are streamed to disk, so memory doesn’t grow with package size; keep free space in
/tmpof at least twice your largest package.
Deploy
Section titled “Deploy”git clone https://github.com/patchkite/patchkite.gitcd patchkite/dockercp .env.prod.example .env# fill in PATCHKITE_DOMAIN, POSTGRES_PASSWORD, and S3_*docker compose -f docker-compose.prod.yml up -dcurl https://patchkite.example.com/health # {"status":"ok"}Create the first admin
Section titled “Create the first admin”The first account on a server becomes an admin, even with ALLOW_REGISTRATION=false:
After that, registration is closed and admins create accounts from the dashboard (Users) or the CLI:
patchkite admin user lsUsers change their temporary password from the dashboard (Account) or with patchkite change-password.
Alternative: a single host behind a tunnel
Section titled “Alternative: a single host behind a tunnel”If HTTPS is handled elsewhere — Cloudflare Tunnel, an existing Nginx, or a load balancer — use docker/docker-compose.selfhost.yml. It runs the server, PostgreSQL, and RustFS on one host, exposes only the server port, and streams package downloads through the server (BLOB_DOWNLOAD_MODE=proxy) so storage never needs to be public.
mkdir -p ~/patchkite/backups && cd ~/patchkite# copy docker/docker-compose.selfhost.yml here as docker-compose.yml, then create .env:umask 077; cat > .env <<ENVPUBLIC_URL=https://patchkite.example.comPATCHKITE_IMAGE=ghcr.io/patchkite/server:latestPATCHKITE_PORT=3000ALLOW_REGISTRATION=falsePOSTGRES_PASSWORD=$(openssl rand -hex 24)S3_ACCESS_KEY_ID=patchkiteS3_SECRET_ACCESS_KEY=$(openssl rand -hex 24)ENVdocker compose up -dThen point your tunnel or proxy at http://<host>:3000 and set TRUST_PROXY=true.
Before you ship to production apps
Section titled “Before you ship to production apps”-
https://<domain>/healthreturns{"status":"ok"}with a valid certificate. -
ALLOW_REGISTRATION=false, and the admin account exists. - CI uses an access key created with
patchkite access-key add <name> --ttl 365d, not a login session. - Code signing is enabled.
- Package downloads work from a real device on a mobile network.
- Database backups are scheduled and a restore has been tested (Maintenance).
- Bucket versioning or replication is enabled.