Skip to main content

Container images

Ouroboros ships as four images, published to registry.apiome.dev. This page lists each one: what it runs, the port and user it runs as, and the variables it needs. Every variable links to its entry in the Configuration reference, which is generated from the services' own configuration file and is the complete list.

The registry​

ImageWhat it runs
registry.apiome.dev/ouroboros-dbThe database migrations. Runs once per deploy, then exits.
registry.apiome.dev/ouroboros-engineThe engine: estimation and the work the REST service hands it.
registry.apiome.dev/ouroboros-restThe REST service: the API, sign-in, scheduling and the farm gateway's paths.
registry.apiome.dev/ouroboros-uiThe web app people use.

Each is tagged latest, and with the full commit hash it was built from. Pin a deployment to the commit tags, and pin each image to its own: the images are rebuilt only when their own part of Ouroboros changes, so they usually carry different hashes — see Operations.

The registry asks for a login before it serves anything:

docker login registry.apiome.dev

The credentials come from whoever provides your Ouroboros images. Log in on every host that pulls, and in the deploy pipeline that runs docker compose pull.

Two more images come from Docker Hub and need no login: postgres:17-alpine for the database, and ollama/ollama if you run a local model.

Values you choose​

Values in <angle brackets> below are yours to pick. Generate every secret, for example with openssl rand -base64 32. The development values in the repository's .env.example are placeholders and must not be deployed.

The two secrets that are checked for length, OURO_ENGINE_SHARED_SECRET and BETTER_AUTH_SECRET, need at least 16 characters. The vault key is special: OURO_VAULT_MASTER_KEY must be exactly 32 bytes, base64-encoded, which is what openssl rand -base64 32 gives you.

db — PostgreSQL 17​

Image postgres:17-alpine, from Docker Hub. Keep its data on a volume at /var/lib/postgresql/data.

VariableValueNotes
POSTGRES_USER<db user>Read on the volume's first start only.
POSTGRES_PASSWORD<db password>Secret.
POSTGRES_DBouroboros

A PostgreSQL server you already run works just as well. Create the database and a user who owns it, and point the migrations and the REST service at it with the variables below.

ouroboros-db — the migrations​

This image is a task, not a service. It holds the migrations and the Flyway configuration that applies them. It starts, applies whatever is pending, and exits; its exit status is the answer, so it has no health check. Run it once per deploy, after the database is up and before the REST service starts.

It runs as the unprivileged nobody user and connects by host and port:

VariableRequiredValue
OURO_DB_HOSTyesdb — the database's name on the network. There is no default, on purpose: localhost inside a container is the container itself.
OURO_DB_USERyes<db user>
OURO_DB_PASSWORDyes<db password>. It is passed to Flyway through the environment, never on a command line.
OURO_DB_PORTno5432
OURO_DB_NAMEnoouroboros
OURO_DB_SCHEMAnoouroboros

Its default command is migrate. Give it info instead to list applied and pending migrations, or validate to check them, as in docker run --rm … ouroboros-db:latest info.

Never run the development seed in production

The image also carries the development seed — demonstration workspaces, people and passwords. It is inert unless you name it on the command line, and the compose page never does. Do not pass -configFiles=…flyway.seed.toml to a production database.

ouroboros-engine​

Listens on 8000 on all interfaces, as the engine user. Its container health check calls GET /healthz.

VariableRequiredValueNotes
OURO_ENGINE_SHARED_SECRETyes<engine secret>Must equal the REST service's value. The engine refuses to start without it.
OURO_LOG_LEVELnoinfodebug, info, warning or error.
PORTno8000Already set in the image.

Leave OURO_RUN_SIMULATOR_SECRET unset. It exists for a development-only simulated-run driver that is not in the production image.

ouroboros-rest​

Listens on 4000, as the nestjs user. The image sets NODE_ENV=production, which is what makes it listen on all interfaces rather than loopback. Its container health check calls GET /health/live; GET /health/ready also checks the database and the engine — see Health checks.

Required​

The REST service refuses to start without any of these, and names the one at fault.

VariableValueNotes
OURO_DATABASE_URLpostgresql://<db user>:<db password>@db:5432/ouroborosSecret.
OURO_ENGINE_URLhttp://engine:8000The engine's internal address.
OURO_ENGINE_SHARED_SECRET<engine secret>The same value as the engine.
OURO_UI_URLhttps://app.example.comWhere a browser lands after signing in or out.
OURO_REST_URLhttp://rest:4000The REST service's own address. It also stands in for OURO_FARM_PUBLIC_URL when that is unset, so set that too if you run a farm.
BETTER_AUTH_URLhttps://app.example.comThe address sign-in URLs are built from — the app's public address in this layout, not the REST service's. Being https:// is also what gives the session cookie its __Secure- prefix.
BETTER_AUTH_SECRET<auth secret>Signs sessions. Rotating it signs everyone out.
OURO_CORS_ORIGINShttps://app.example.comThe browser origins the service trusts, comma-separated. No wildcard.
OURO_GITHUB_CLIENT_ID<from your GitHub OAuth app>Register the app with the callback https://app.example.com/api/auth/callback/github.
OURO_GITHUB_CLIENT_SECRET<from your GitHub OAuth app>Secret.
OURO_VAULT_MASTER_KEY<exactly 32 bytes, base64>Encrypts every stored provider credential. Losing it loses every stored credential, so back it up separately from the database.

The build farm​

Only if you run ouroboros-runner on build machines. Leave all four unset otherwise.

VariableValueNotes
OURO_FARM_PUBLIC_URLhttps://farm.example.comThe address written into the installer. https:// only.
OURO_FARM_CLIENT_CERT_HEADERx-ouro-client-certThe header your farm gateway forwards the client certificate in. Set it only if that header can reach the REST service through your gateway and from nowhere else — see The farm gateway.
OURO_FARM_RELEASES_DIR/runner-releasesA volume holding the ouroboros-runner releases the installer serves, one directory per version.
OURO_FARM_MIN_AGENT_VERSIONfor example 0.5.0The oldest agent version accepted.

Build jobs upload their artifacts — test reports, captures, logs — to the REST service:

VariableValueNotes
OURO_ARTIFACT_STORElocal or s3local keeps them on a volume at OURO_ARTIFACT_DIR, which defaults to /app/.artifacts in the image — mount a volume there, or they are lost with the container. A volume is one disk, so with more than one REST replica use s3, with OURO_ARTIFACT_S3_ENDPOINT, OURO_ARTIFACT_S3_BUCKET, OURO_ARTIFACT_S3_ACCESS_KEY_ID and OURO_ARTIFACT_S3_SECRET_ACCESS_KEY. The bucket must exist.

Optional​

VariableValueNotes
OURO_LOCAL_PROVIDER_URLSollama=http://ollama:11434Only with a local model host. The address must be reachable from the engine.
OURO_GITHUB_API_BASE_URLhttps://ghe.example.com/api/v3GitHub Enterprise Server only.
OURO_SMTP_URL, OURO_MAIL_FROMsmtps://user:password@smtp.example.com:465, no-reply@example.comOnly for the weekly Insights digest and decision mail. Set both or neither — see Notifications.

Everything else — poll intervals, sweeps, retention, budgets — has a working default. The Configuration reference lists them all.

ouroboros-ui​

Listens on 3000, as the nextjs user. Its container health check calls GET /. The image holds no service address: it reads the REST service's address at request time, so one image serves every environment.

VariableRequiredValueNotes
OURO_REST_URLyeshttp://rest:4000The REST service's internal address. The app calls it from its own server and never sends it to a browser.
PORTno3000Already set in the image.

ollama — optional​

Image ollama/ollama, from Docker Hub, with a volume at /root/.ollama for the models it pulls. It needs no Ouroboros configuration of its own; the REST service learns its address from OURO_LOCAL_PROVIDER_URLS. Do not publish its port.

What can go wrong​

  • docker pull answers no basic auth credentials — the registry needs a login. Run docker login registry.apiome.dev on that host first.
  • The REST service exits at start naming a variable — it is missing or malformed. The tables above say what each one takes; the Configuration reference has the details.
  • ouroboros-db exits 2 naming OURO_DB_HOST — the variable is unset. It has no default.
  • The engine reports up, but every run fails at once — the two copies of OURO_ENGINE_SHARED_SECRET differ. Set the same value on both services.
  • The app says it cannot reach the REST service — OURO_REST_URL on ouroboros-ui must be the internal address, http://rest:4000, not the app's public one.