Skip to main content
Version: Next

Running O2ID with Docker

O2ID publishes an official Docker image built from the same static binary the installer ships — every tagged release publishes a matching multi-arch (linux/amd64, linux/arm64) image to GitHub Container Registry:

docker pull ghcr.io/o2stack/o2id:latest
note

There is deliberately no o2idctl in this image. o2idctl is a client tool that talks over HTTP to whatever O2ID instance you point it at — it isn't a component of the running service, so it isn't bundled here. Install it separately via the installer wherever you run management commands from.

O2ID's application-level encryption for connection secrets (see Managing Connections) uses O2ID_MASTER_KEY. If it's unset, O2ID generates one and saves it to /var/lib/o2id/o2id.master.key (inside the mounted volume, so it survives a container restart) rather than refusing to start — but for a real deployment, set it explicitly instead: it's one less thing depending on volume persistence for something this sensitive, and it lets you control your own key rotation and backup process. Generate one and keep it somewhere durable (losing it makes any stored connection secrets and tenant signing keys unrecoverable):

openssl rand -hex 32

Running the container

docker run -d \
--name o2id \
-p 8080:8080 \
-e O2ID_MASTER_KEY=<your-generated-key> \
-v o2id-data:/var/lib/o2id \
ghcr.io/o2stack/o2id:latest
  • -v o2id-data:/var/lib/o2id persists the default SQLite database file across container restarts/recreations — omit it only for a throwaway instance. If you bind-mount a host directory instead of a named volume, make sure Docker is actually allowed to share that path (Docker Desktop and Rancher Desktop don't share /tmp by default on macOS — use a path under your home directory, or a named volume, to avoid data silently not persisting).
  • To use PostgreSQL or MySQL instead, mount your own o2id.toml over /etc/o2id/o2id.toml (see Storage) rather than the SQLite default baked into the image.

First run: setting up the platform admin

The first time O2ID starts against an empty database, it prompts on stdin for the platform admin's email and password (see Setting up the platform admin). In a container this means the first run needs stdin attached:

docker run -i --rm \
-p 8080:8080 \
-e O2ID_MASTER_KEY=<your-generated-key> \
-v o2id-data:/var/lib/o2id \
ghcr.io/o2stack/o2id:latest

Type the email and password when prompted (or pipe them, for scripted setup — the prompt falls back to plain-text stdin when it isn't attached to a real terminal):

printf '%s\n%s\n' admin@example.com 'a-strong-password' | \
docker run -i --rm \
-p 8080:8080 \
-e O2ID_MASTER_KEY=<your-generated-key> \
-v o2id-data:/var/lib/o2id \
ghcr.io/o2stack/o2id:latest

Once the platform admin exists (persisted in the mounted volume), every subsequent run can start normally detached (-d, no -i needed) — the prompt only appears when the user table is empty.

Verify it's running

curl http://localhost:8080/health

Building the image yourself

The Dockerfile at the repository root builds the same binary go build ./cmd/o2id does, just inside a container:

docker build --build-arg VERSION=$(git describe --tags --always) -t o2id:local .

Configuration reference

SettingHow
Listen addressBaked in as --addr :8080; publish a different host port with -p <host>:8080 rather than changing this.
Config file (storage driver, external portal)Bind-mount your own file over /etc/o2id/o2id.toml — see Storage and Login Portal.
Master keyO2ID_MASTER_KEY environment variable — recommended; auto-generated into the volume if omitted (see above).
Data directory/var/lib/o2id (also the container's working directory, so a relative dsn in o2id.toml resolves here).