Skip to main content

Install

Install

For a quick try on your laptop without domains or TLS, start with the Local quickstart (cmpatch selfhost local-eval).

To deploy with your own domains:

  • A server you can reach over SSH
  • Two hostnames you can add DNS records for — usually two subdomains of one domain you already own, such as updates.example.com and storage-updates.example.com
  • A GitHub OAuth App for sign-in (a couple of clicks; the installer walks you through it)
  • For production, a CDN in front of storage — Cloudflare or CloudFront; strongly recommended, the installer offers it as a step

cmpatch selfhost install handles the rest: it checks the server, offers to install Docker, git, and curl if they are missing, walks you through DNS and the OAuth app, and then runs the installer over SSH — OAuth wiring, Compose stack, TLS certificates, secrets, Postgres, MinIO, and the dashboard. The Docker bootstrap covers the distributions Docker's own install script supports (Ubuntu, Debian, Fedora, RHEL, CentOS, Rocky, and their derivatives) plus Amazon Linux 2023; on anything else, install Docker Engine with the Compose plugin yourself and run the command again.

Choose storage

The default install stores artifacts in MinIO on the same host. You can choose Cloudflare R2, Amazon S3, or Google Cloud Storage instead. The installer can create those buckets or walk you through the console.

External storage uses two buckets: one public for downloads, one private for _internal/ objects. Unattended flags, and how to reuse buckets after a failed setup, are in the CLI command reference. Adapter env vars are in Infrastructure adapters.

Machine size

The default Compose install runs Caddy, the API and release worker, Postgres, and MinIO on one host. You need Docker + Docker Compose v2, with ports 80 and 443 open.

  • RAM: 2 GB is enough for typical use. Prefer 4 GB for large or overlapping releases, or a long on-box history of artifacts and metrics.
  • CPU: 1 vCPU is enough for typical use.
  • Disk: tens of GB is fine to start. Object storage grows with retained releases; Postgres grows with device metrics (metric_event is not auto-pruned).

GitHub OAuth App

Dashboard and CLI sign-in go through a GitHub OAuth App. You do not need to create it in advance: the installer opens a pre-filled registration form at the right moment and checks the credentials with GitHub before it builds anything. Bitbucket Cloud OAuth is a supported alternative for on-server installs (see below).

info

The administrator's email must exactly match the verified primary email on their GitHub (or Bitbucket) account. The default registration mode is invite_only, so the very first sign-in is rejected if it doesn't match.

Run the installer

Install the CLI on your own machine (Node.js >=20) and run:

npm install -g @codemagic/patch-cli

cmpatch selfhost install

The command asks for everything it needs — the server, the two hostnames, the administrator email, the GitHub OAuth app — and waits at each step until it checks out, so there is nothing to prepare in advance. Building the images takes around twenty minutes on a cold cache. If the server is not reachable over SSH, see Troubleshooting.

You can also start from the app: cmpatch init in a React Native project offers to install a server, signs you in, and links the project in one run.

When it finishes you'll have:

Dashboard: https://updates.example.com/
API URL: https://updates.example.com (app config: CodemagicPatchApiUrl)
Download base: https://storage-updates.example.com/codemagic-patch (app config: CodemagicPatchDownloadBaseUrl)

Sign in to the dashboard with GitHub to create the administrator account, then connect your machine with cmpatch login and create apps and deployments (or use the dashboard). See Web dashboard.

warning

.env.selfhost on the server holds production secrets. Back it up and never commit or expose it.

On-server installs

Every answer has a flag, so the same install runs unattended. An unattended run skips the interactive DNS step, so add the two records and let them resolve first — otherwise the install fails when the server cannot obtain its certificates:

# the secret travels in the environment, never in a command line
export GITHUB_OAUTH_CLIENT_SECRET=<github_client_secret>

cmpatch selfhost install ubuntu@203.0.113.7 \
--non-interactive \
--api-domain updates.example.com \
--storage-domain storage-updates.example.com \
--email admin@example.com \
--github-oauth-client-id Iv1.xxxxxxxxxxxxxxxx \
--install-docker

The CLI is a wrapper around a script in the repository; you can run that script on the host directly instead. You then add the DNS records yourself, register the OAuth App by hand, and run the checks the guided installer would run for you — machine size, open ports, DNS, the OAuth pair:

OAuth App settingValue
Homepage URLhttps://updates.example.com
Authorization callback URLhttps://updates.example.com/auth/callback
git clone https://github.com/codemagic-ci-cd/codemagic-patch.git
cd codemagic-patch

scripts/selfhost/install.sh \
--api-domain updates.example.com \
--storage-domain storage-updates.example.com \
--email admin@example.com \
--github-oauth-client-id Iv1.xxxxxxxxxxxxxxxx \
--github-oauth-client-secret <github_client_secret>

To sign in with Bitbucket Cloud instead of (or as well as) GitHub, pass --bitbucket-oauth-client-id / --bitbucket-oauth-client-secret; at least one provider is required.

To run this same stack on your own machine over plain HTTP — a laptop evaluation with real OAuth sign-in but no certificate, DNS, or open ports — add --allow-http to the script. It serves the machine it runs on alone: the server is http://localhost, downloads are http://localhost:9110, the OAuth callback is http://localhost/auth/callback, and the app must allow cleartext traffic to those URLs. Any other hostname or IP is refused with the flag, because a server other machines reach needs the HTTPS install; for the same reason cmpatch selfhost install, which installs over ssh, does not take it. See the full guide for what changes.

Either way, the installer writes .env.selfhost with strong random secrets (it refuses to overwrite an existing file), builds the server and Caddy images, starts the Compose stack under project name codemagic-patch-selfhost, waits for Caddy to obtain Let's Encrypt certificates (a --allow-http install instead waits for the sites to answer over plain HTTP), and prepares the single fixed default-team on first boot.

Verify

curl -fsS https://updates.example.com/health
curl -fsS https://storage-updates.example.com/minio/health/ready

# Unauthenticated smoke test
scripts/selfhost/smoke.sh

# After you create an API token, run the full publish smoke test
CODEMAGIC_PATCH_TOKEN=cm_pat_xxx scripts/selfhost/smoke.sh
tip

Before you rely on this stack in production, run cmpatch selfhost backup --download and keep the copy somewhere other than the server. For backups, upgrades, and what to watch next, see Ongoing maintenance. Command reference: Operations.

CDN delivery

A CDN is optional for trying Patch, but for production we strongly recommend one: without it every device downloads straight from the single storage host, which absorbs all traffic and has no edge cache. Front the storage domain with a CDN so artifacts and manifests are served from the edge and Patch can purge stale manifests after releases. cmpatch selfhost install offers this as a step: it lists Cloudflare when your download domain is already on a Cloudflare account, and CloudFront always.

The guided steps cover token creation, the DNS proxy switch, the cache rule, and — for CloudFront — the AWS Console walkthrough and the final DNS cutover, including the checks that must pass before it. The reference walkthroughs are Cloudflare setup and CloudFront setup. The guided installer configures the bundled Postgres/MinIO topology. CloudFront can also front external S3/GCS storage, but that topology starts with the direct install.sh path in Infrastructure adapters, then follows the external-storage track in the CloudFront guide.

Free Cloudflare plans

Prefer a sibling storage hostname (storage-updates.example.com rather than storage.updates.example.com): nested hostnames work, but on Cloudflare Free they need Advanced Certificate Manager (Universal SSL only covers *.example.com). Details: Cloudflare setup.