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
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.
Required: the master key
O2ID's application-level encryption for connection secrets (see
Managing Connections) requires
O2ID_MASTER_KEY to be set — the server refuses to start without it,
regardless of whether you use the Connections feature yet. Generate one and
keep it somewhere durable (losing it makes any stored connection secrets
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/o2idpersists 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/tmpby 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.tomlover/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
make service does, just inside a container:
docker build --build-arg VERSION=$(git describe --tags --always) -t o2id:local .
Configuration reference
| Setting | How |
|---|---|
| Listen address | Baked 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 key | O2ID_MASTER_KEY environment variable — required, no default. |
| Data directory | /var/lib/o2id (also the container's working directory, so a relative dsn in o2id.toml resolves here). |