---
title: "Docker"
description: "One command to try it: a server, a node, the console and a sample plugin in one container. And a compose file for a small team: PostgreSQL, a server and a node."
canonical_url: "https://docs.oz0.ai/fleet/docker"
---
# Docker

> One command to try it: a server, a node, the console and a sample plugin in one container. And a compose file for a small team: PostgreSQL, a server and a node.

For: cluster operators.

Use the supplied Compose layout to start the deployment. Preserve its keys and data volumes when replacing containers.

Two ways to run Orchestrator Zero with Docker, both from images you build locally (no official images are published yet):

- The quickstart, one container with a dev server, the console, a node and a sample plugin, to try it.
- Compose, for a small team on one machine: PostgreSQL, one server in cluster mode and one node, each in its own container with its own volume. More nodes join from other machines.

## Try it in one command

Building the image currently requires access to the private repository. Public source downloads and official images are coming soon. Build the image once, from your checkout:

```bash [Terminal]
make image-quickstart        # orchestrator-zero:quickstart, built locally and never pushed
```

and run it:

```bash [Terminal]
docker run --rm -p 8443:8443 orchestrator-zero:quickstart
```

It starts a [dev server](https://docs.oz0.ai/fleet/dev-mode), joins a node to it, installs the sample plugin from `examples/greeter`, runs a first job, and prints where to sign in:

```text
Orchestrator Zero is running.

    Web UI    https://localhost:8443
    User      admin
    Password  lZDqnGomjNqVdXWNxiW6sZVr

  The certificate comes from the cluster's own CA, so the browser asks you to accept it once.
  A first job ran on the node: "Hello, quickstart! (from 1f0c2a7e9b3d)"
```

Everything the node needs is in the image: uv, Python, the runtime and the sample plugin's packages. The container starts in about 15 seconds and needs no network. The CLI is in the container too:

```bash [Terminal]
docker exec -it <container> oz0 node list
docker exec -it <container> oz0 job run --task-queue oz0.default --workflow HelloWorkflow --input '"you"'
```

The container's data, the dev cluster with its keys, lives in `/var/lib/oz0`. To keep it when the container goes, give it a named volume, which Docker fills with what the image holds there the first time:

```bash [Terminal]
docker run -d --name oz0 -p 8443:8443 -v oz0-quickstart:/var/lib/oz0 orchestrator-zero:quickstart
```

The quickstart is a dev server on SQLite with its node in the same container: for trying, demos and plugin development on one machine, not for a team's work or nodes on other machines. For those, use compose.

## Compose for a small team

`deploy/compose/compose.yaml` runs a [cluster](https://docs.oz0.ai/fleet/cluster) with one server:

<table>
<thead>
  <tr>
    <th>
      Service
    </th>
    
    <th>
      What
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        postgres
      </code>
    </td>
    
    <td>
      PostgreSQL 16, its data in the volume <code>
        postgres
      </code>
      
      ; healthy when it takes connections over TCP, so not during the first start's temporary server
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        init
      </code>
    </td>
    
    <td>
      runs once per <code>
        up
      </code>
      
      : makes the master key and the admin's password in the volume <code>
        secrets
      </code>
      
       the first time, then <code>
        server init --exist-ok
      </code>
      
      , which creates the cluster once and afterwards checks that the key still unseals it
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        server
      </code>
    </td>
    
    <td>
      <code>
        server start
      </code>
      
       in cluster mode: management, the edge and Temporal; healthy when its edge is ready (<code>
        oz0 server healthcheck
      </code>
      
      ). Its <a href="https://docs.oz0.ai/govern/personal-data/retention#backups">
        erasure ledger
      </a>
      
       is in the volume <code>
        ledger
      </code>
      
      , apart from the database
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        token
      </code>
    </td>
    
    <td>
      runs once per <code>
        up
      </code>
      
      : a join token for one use, while the node has not joined
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        node
      </code>
    </td>
    
    <td>
      joins once with that token, then keeps its identity, uv, Python and runtime in the volume <code>
        node
      </code>
    </td>
  </tr>
</tbody>
</table>

Build the images and set a password for PostgreSQL:

```bash [Terminal]
make images IMAGE_TAG=local
cp deploy/compose/.env.example deploy/compose/.env
sed -i "s/^OZ0_POSTGRES_PASSWORD=.*/OZ0_POSTGRES_PASSWORD=$(openssl rand -hex 24)/" deploy/compose/.env
```

Start it, and read the admin's password:

```bash [Terminal]
docker compose -f deploy/compose/compose.yaml up -d --wait
docker compose -f deploy/compose/compose.yaml exec server cat /oz0/secrets/admin-password
```

Sign in at `https://<this machine>:8443` as `admin`. The CLI works from the server's container with the operator credentials `init` made:

```bash [Terminal]
docker compose -f deploy/compose/compose.yaml exec -e OZ0_CONTEXT=/oz0/secrets/operator/context.json server oz0 node list
```

Back up the master key now and keep it somewhere else: without it the cluster's CAs and secrets cannot be unsealed. [Backups](https://docs.oz0.ai/fleet/upgrades) of the database need it too.

```bash [Terminal]
docker compose -f deploy/compose/compose.yaml cp server:/oz0/secrets/master.key ./oz0-master.key
```

`.env` holds the settings: `OZ0_TAG` (the images' tag), `OZ0_POSTGRES_PASSWORD`, `OZ0_CLUSTER_NAME`, `OZ0_HISTORY_SHARDS` (64; it [can never change](https://docs.oz0.ai/fleet/cluster#choose-the-history-shards-carefully)), `OZ0_PUBLIC_HOST` (the name other machines reach this one at, for the server's certificate), `OZ0_PUBLISH_IP` and the three ports, and `OZ0_NODE_NAME`.

`docker compose down` and `up` again keep everything: `init` finds the cluster, and the node comes back as itself without joining again. `down -v` removes the volumes, and with them the cluster. Back up the volume `ledger` apart from the database: a restore of an older backup applies again the erasures it records.

### More nodes

Nodes on other machines join the edge on ports 7443 and 7233 with the console's *Add nodes* command, and run there [as services](https://docs.oz0.ai/fleet/services). Set `OZ0_PUBLIC_HOST` to the name they reach this machine at, and restart the server (`docker compose up -d`), so that its certificate names it.

The command in the console names the server's address inside Docker's network (such as `https://172.18.0.3:7443`), which other machines cannot reach: replace it, both times it appears, with `https://<OZ0_PUBLIC_HOST>:<OZ0_EDGE_API_PORT>`. The node keeps using the address it joined through, and reaches Temporal at the same host on port 7233, the port the edge reports: for nodes on other machines, keep `OZ0_EDGE_TEMPORAL_PORT` at 7233.

### When the team grows

Compose runs one server on one machine. For two or more servers, an external PostgreSQL and backups on a schedule, use the [Helm chart](https://docs.oz0.ai/fleet/kubernetes) or [run the servers yourself](https://docs.oz0.ai/fleet/cluster).


## Sitemap

See the full [sitemap](https://docs.oz0.ai/sitemap.md) for all pages.
