Container Images¶
Mission Control builds one OCI image per service from deploy/images/Dockerfile: gateway, console, agent-runtime, collector, and migrate, which applies database migrations and exits. Tagged releases publish them, signed and with SBOMs, as ghcr.io/ziadmoubayed/mission-control-<target>:<version>; you can also build them yourself. The images carry no configuration, secrets or test fixtures: configuration files and secrets are supplied when a container starts.
This page covers building and running the images. Orchestration manifests (a Helm chart and an infrastructure module) are planned work, not part of this release; see Planned capabilities.
Prerequisites¶
- Docker 24 or later with Buildx. Building both architectures on one machine also needs QEMU emulation (
docker run --privileged --rm tonistiigi/binfmt --install arm64,amd64). - A PostgreSQL 16 database created with the libc
Ccollation and character type, as Bootstrap describes, and the settings in Configure the services and Environment variables.
Build the images¶
From the repository root:
Expected result: Built 5 targets for 0.1.0, and docker image ls lists registry.example.test/acme/mission-control-<target>:0.1.0 for each target. Without IMAGE_LOAD=1 the script builds linux/amd64 and linux/arm64 and discards the result, which checks that every target builds. With --push it pushes both architectures and writes .images/digests.txt. --tag vX.Y.Z refuses a tag that does not equal v plus the VERSION file, for example tag v0.2.0 does not match VERSION 0.1.0 (expected v0.1.0).
The Console build needs about 4 GiB of memory in the builder.
Run the images¶
Every image runs as the numeric user 10001, needs no capabilities, and works on a read-only root filesystem when the paths below are writable:
| Image | Listens on | Writable paths | Start command |
|---|---|---|---|
gateway |
4000 inference, 4001 private control |
/tmp |
the gateway server |
console |
3000, and CONSOLE_INTERNAL_PORT if set |
/tmp, /app/platform/apps/console/.next/cache |
next start -H 0.0.0.0 -p 3000 |
agent-runtime |
4100 (binds 0.0.0.0 in the image) |
/tmp |
mission-control-agent-runtime |
collector |
4317 gRPC, 4318 HTTP, 13133 health (/health) |
none | the Collector with /etc/mission-control/collector.yaml |
migrate |
nothing | /tmp |
the migration command; exits when done |
Set the listener hosts to 0.0.0.0 in containers (INFERENCE_GATEWAY_HOST, CONTROL_GATEWAY_HOST, CONSOLE_INTERNAL_HOST, and TELEMETRY_HEALTH_ENDPOINT=0.0.0.0:13133 for the Collector) and keep the gateway's 4001, the Console's internal port and the Agent runtime's 4100 on a private network. Mount the Organization configuration and point MISSION_CONTROL_CONFIG at it; the images carry only the example configuration under /app/platform/config.
Two service-to-service rules decide how containers are placed:
- Internal calls use HTTPS unless they stay on loopback. The Console's control-gateway URLs, the Agent runtime's gateway, control and OTLP endpoints, and the Collector's
MISSION_CONTROL_INTERNAL_URLaccepthttp://only for127.0.0.1,localhostor::1; any otherhttp://address fails startup (for exampleOTLP endpoint requires HTTPS outside loopback). Either serve the gateway's listeners behind TLS on the private network, or run the caller in the same network namespace as the gateway (one pod, or Docker's--network container:<gateway>). - The Collector serves OTLP only over TLS. Mount a certificate and key readable by user
10001and setOTLP_TLS_CERT_FILEandOTLP_TLS_KEY_FILE.
Apply migrations before starting a release, with the same DATABASE_URL the services use:
docker run --rm --read-only --tmpfs /tmp \
-e DATABASE_URL=postgres://mission_control@db.example.test:5432/mission_control \
registry.example.test/acme/mission-control-migrate:0.1.0
Expected result: Applied N migration(s); M recorded in total. A second run applies nothing. The gateway and Console refuse to start with STORAGE_MIGRATION_REQUIRED until migrations are current. For a new installation, bootstrap the first Organization from the Console image, which also migrates:
docker run --rm --read-only --tmpfs /tmp \
-e DATABASE_URL=postgres://mission_control@db.example.test:5432/mission_control \
-e BOOTSTRAP_ADMIN_EMAIL=owner@example.test \
-e BOOTSTRAP_ORGANIZATION_NAME=Acme -e BOOTSTRAP_ORGANIZATION_SLUG=acme \
registry.example.test/acme/mission-control-console:0.1.0 \
node /app/platform/node_modules/tsx/dist/cli.mjs src/cli/bootstrap.ts
The gateway refuses to start while its configuration names an Organization bootstrap has not created (Configured organization is not bootstrapped).
The images set MISSION_CONTROL_VERSION; do not override it. Read it as described in Versions and compatibility.
Verify the images¶
tests/images/smoke.sh starts every image against a throwaway PostgreSQL on a private Docker network, as the release workflow does before publishing. From the repository root, after building with IMAGE_LOAD=1:
Expected result: one ok: line per check and Image smoke test passed for 0.1.0. It checks that each image runs as 10001:10001, that migrate brings a fresh database current and a second run applies nothing, that bootstrap from the Console image creates the example Organization, that the gateway, Console, Agent runtime and Collector answer health on a read-only filesystem, and that the gateway's readiness reports the Console's version. It needs Docker and OpenSSL on the host, and it does not send inference traffic. On a failure it prints the last log lines of every container it started, and it always removes them.
Failure recovery¶
| Symptom | Recovery |
|---|---|
tag vX.Y.Z does not match VERSION |
Tag the commit whose VERSION holds that version, or build without --tag. |
| The Console build is killed | Give the builder at least 4 GiB of memory. |
A container exits with EROFS or read-only file system |
Mount the writable paths in the table above. |
STORAGE_MIGRATION_REQUIRED |
Run the migrate image against the same DATABASE_URL, then start the service again. |
MISSION_CONTROL_VERSION must be a semantic version |
Remove the override; the image sets it. |
storage_collation_refused |
Create the database with the libc C collation and character type; see Bootstrap. |
... requires HTTPS outside loopback or Control gateway URL is invalid |
Use HTTPS for that internal URL, or run the caller in the gateway's network namespace. |
The Collector exits with TLS configuration must include both certificate and key |
Mount a certificate and key and set OTLP_TLS_CERT_FILE and OTLP_TLS_KEY_FILE. |