Installation
Start the control plane on one Linux machine with Docker Compose and prebuilt images.
The control plane runs on one Linux machine from prebuilt images: Postgres, migrations, the API server and Caddy for TLS. Nothing is compiled.
Check what you need
| Need | Detail |
|---|---|
| A Linux machine | linux/amd64, with Docker Engine and the Compose plugin |
| A DNS name for the API | An A record such as api.example.com pointing at this machine |
| Open ports | 80 and 443, for the API and the Let's Encrypt HTTP-01 challenge |
| A private address | The address your sandbox hosts reach this machine on (VPC, VPN or tailnet) |
Pick a version
Both images, ghcr.io/kortix-ai/platinum-api and ghcr.io/kortix-ai/platinum-web, always run at the same tag.
| Tag | Moves? | Use |
|---|---|---|
X.Y.Z | Never | Production: pin one |
latest | To each new release | Trying the newest release |
edge | To every main commit | Following development |
sha-<12 hex> | Never | One exact main commit |
A release tag is cut from a commit already built on main, so X.Y.Z and that commit's sha- tag are the same image. Use edge until a release you want exists.
Write the settings
setup.sh writes .env with mode 600 and generates every secret.
git clone https://github.com/kortix-ai/platinum.git
cd platinum/infra/compose
./setup.sh --domain api.example.com --email you@example.com --version X.Y.ZUse your own hostname: setup.sh refuses the api.example.com placeholder.
| Flag | Effect |
|---|---|
--domain | The API hostname Caddy serves. Required on the first run. |
--email | Added to PT_ADMIN_EMAILS, and used as the Let's Encrypt contact. |
--version | The image tag. Defaults to the release a vX.Y.Z checkout is at, else the value in .env. |
--private-ip | The address sandbox hosts reach this machine on. Detected when omitted. |
--sbx-domain | Turns on preview URLs. See Domains & TLS. |
--console-domain | Turns on the web console. See Console. |
--no-s3 | Drops the bundled object store, when you bring your own. |
--build | Builds both images from this checkout instead of pulling them. |
--force | Regenerates every secret. Fresh installs only. |
Re-running setup.sh keeps every secret, and a flag you leave out keeps its current value. So it is safe to run again to change one setting.
Start the stack
Compose starts postgres, then migrate, then server, then caddy.
docker compose up -d
docker compose logs migrate # both migration lanes, then "[migrate] done"
curl https://api.example.com/healthA failed migration stops the rollout: the server never starts against a schema it does not understand.
Create the bucket
With the bundled object store, create the bucket once after the first start.
docker compose exec s3 sh -c 'echo "s3.bucket.create -name platinum" | weed shell'Skip this with --no-s3. See Object storage.
Firewall the shared ports
Postgres (:5432) and the bundled store (:9000) are published on --private-ip, because sandbox hosts read both. Allow only your hosts to reach them.
setup.sh prints a note when that address is public. Docker-published ports bypass ufw, so filter them in the DOCKER-USER chain or keep hosts on a private network. See Hardening.
Serve plain HTTP on a private network
With no domain, pass an http:// address instead. Caddy then serves plain HTTP.
./setup.sh --domain http://<private-ip> --version X.Y.Zsetup.sh writes PT_PUBLIC_API_URL, PT_AUTH_BASE_URL and PT_API_HOST for you in this mode. Use it on private networks only.
Build from source
For contributors, or to run unreleased changes:
./setup.sh --domain api.example.com --build
docker compose up -d --build--build stamps the images with the checkout's commit, so re-run setup.sh after each pull. --no-build --version X.Y.Z goes back to the published images. The console build needs about 2 GB of RAM.
Check a stack
smoke.sh starts a throwaway stack from any tag and checks migrations (twice), health, the served installer, the host bundle and the console.
PLATINUM_VERSION=edge ./smoke.sh