> For the complete documentation index, see [llms.txt](https://docs.bitsafe.finance/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.bitsafe.finance/decentralization-manager/get-started/run-with-docker.md).

# Run with Docker

[Prerequisites](/decentralization-manager/get-started/prerequisites.md) · [Run locally](/decentralization-manager/get-started/run-locally.md)

## Outcome

Run one Decentralization Manager node in a container with persistent state. Connect the node to Canton, and confirm that its identity survives a restart. You can then run a three-node mesh with Docker Compose.

This guide is for development. [Production deployment](/decentralization-manager/get-started/production-deployment.md) covers orchestration, TLS, secrets, backups, monitoring and upgrades.

## Before you begin

* Docker Engine with BuildKit. The mesh also needs Docker Compose v2.
* Reachable Canton Admin and Ledger APIs.
* An identity provider for the operator UI: Keycloak or Auth0.
* A writable host directory for the node state.

The operator UI needs a configured identity provider. Without one, the API rejects every request except the first `PUT /party-config` on a fresh node. The UI then cannot load data. Local development has one exception. `--insecure` replaces the identity provider on `devnet`, against a participant with unsafe shared-secret authentication.

{% hint style="warning" %}
Insecure mode turns off authentication. Never enable it on a node that other people can reach. Insecure mode starts only on `devnet`. The node refuses to start in insecure mode on `testnet` or `mainnet`. The network defaults to `devnet` when `DECPM_CANTON_NETWORK` is unset.
{% endhint %}

## 1. Choose an image

| Image                                                            | Source                              | Runs as   | `DECPM_DIR`     | Mount the host directory at |
| ---------------------------------------------------------------- | ----------------------------------- | --------- | --------------- | --------------------------- |
| `public.ecr.aws/dlc-link/decentralization-manager:<tag>`         | Release                             | uid 0     | `/`             | `/data`                     |
| `public.ecr.aws/dlc-link/decentralization-manager:<tag>-nonroot` | Release                             | uid 65532 | `/home/nonroot` | `/home/nonroot/data`        |
| `dec-party-manager:dev`                                          | Built from `development/Dockerfile` | uid 0     | `/`             | `/data`                     |

The node writes its state to `$DECPM_DIR/data`. Mount the host directory there. If you mount it at another path, the state stays inside the container. The state then disappears when you remove the container.

The release images are `linux/amd64` images. Release tags have the form `v<version>`. Pull a release image:

```bash
docker pull public.ecr.aws/dlc-link/decentralization-manager:<tag>-nonroot
```

To build the development image from source instead:

```bash
git clone https://github.com/DLC-link/decentralization-manager.git
cd decentralization-manager
git checkout <release-tag>

docker build --platform linux/amd64 -f development/Dockerfile -t dec-party-manager:dev .
```

The development Dockerfile copies x86-64 system libraries into its runtime stage. Keep `--platform linux/amd64` on an Arm host. The build fetches its Git dependencies over HTTPS from public repositories. It needs no SSH key.

## 2. Prepare persistent state

```bash
mkdir -p ./docker-node/data
```

For the `-nonroot` image, give the directory to uid 65532:

```bash
sudo chown -R 65532:65532 ./docker-node/data
```

The `chown` must be recursive. A directory that a root image wrote holds root-owned files, for example the mode-0600 Noise key. Uid 65532 cannot open those files.

After the first start, the mounted directory holds:

```
noise.key    Noise identity of the node
decpm.db     SQLite database: peers, party credentials, workflow state
```

A new `noise.key` changes the peer identity. A deleted `decpm.db` removes the local peers, party credentials and workflow state.

## 3. Configure the container

Create `./docker-node/node.env`:

```bash
DECPM_PUBLIC_ADDRESS=<address-that-peers-dial>
DECPM_CANTON_ADMIN_HOST=host.docker.internal
DECPM_CANTON_ADMIN_PORT=5002
DECPM_CANTON_LEDGER_HOST=host.docker.internal
DECPM_CANTON_LEDGER_PORT=5001
DECPM_CANTON_SYNCHRONIZER=global
DECPM_CANTON_NETWORK=devnet
DECPM_KEYCLOAK_URL=https://<your-keycloak-host>
DECPM_KEYCLOAK_REALM=<your-realm>
DECPM_KEYCLOAK_CLIENT_ID=<frontend-client-id>
```

* `host.docker.internal` reaches Canton APIs on the Docker host. Use the participant host name when Canton runs elsewhere.
* Replace the three Keycloak variables with `DECPM_AUTH0_DOMAIN`, `DECPM_AUTH0_CLIENT_ID` and `DECPM_AUTH0_AUDIENCE` for Auth0.
* Add the Canton TLS variables when the participant serves TLS. [Configuration reference](/decentralization-manager/operations-and-reference/configuration-reference.md) lists them.
* Write the values without quotes. Docker `--env-file` keeps quotes as part of the value.

Pass settings with `--env-file` or `-e`. The image entrypoint passes no `-d` flag. The node therefore reads `.env` from its working directory. `DECPM_DIR` does not choose the `.env` location. The data mount does not cover the working directory. Do not store production credentials in the env file, and do not commit it.

## 4. Start one container

For the `-nonroot` release image:

```bash
docker run -d \
  --name dec-party-manager \
  --restart unless-stopped \
  --env-file ./docker-node/node.env \
  --add-host host.docker.internal:host-gateway \
  -p 8080:8080 \
  -p 9000:9000 \
  -v "$(pwd)/docker-node/data:/home/nonroot/data" \
  public.ecr.aws/dlc-link/decentralization-manager:<tag>-nonroot
```

For the root image or the development image, mount the directory at `/data` instead.

The HTTP port serves the operator UI. Expose the Noise port only to the intended peers. The command does not publish the metrics port `9464`. The metrics listener has no authentication and binds to `DECPM_HOST`. To read metrics from the host, publish the port on the loopback address only: `-p 127.0.0.1:9464:9464`.

For insecure mode on `devnet`, leave out the identity-provider variables and add `--insecure` after the image name:

```bash
docker run -d \
  --name dec-party-manager \
  --env-file ./docker-node/node.env \
  --add-host host.docker.internal:host-gateway \
  -p 8080:8080 \
  -p 9000:9000 \
  -v "$(pwd)/docker-node/data:/home/nonroot/data" \
  public.ecr.aws/dlc-link/decentralization-manager:<tag>-nonroot --insecure
```

The image entrypoint is `dec-party-manager serve`. Docker therefore passes `--insecure` to `serve`.

## 5. Verify startup and persistence

```bash
docker ps --filter name=dec-party-manager
docker logs --tail 100 dec-party-manager
curl -s http://localhost:8080/healthz
ls -la ./docker-node/data

docker restart dec-party-manager
docker logs --tail 100 dec-party-manager
ls -la ./docker-node/data
```

Confirm that:

* `/healthz` returns `{"status":"ok"}`.
* The log shows `Noise listener started on` and `Starting HTTP server on`.
* The operator UI opens and uses the configured identity provider.
* The same `noise.key` and `decpm.db` remain after the restart.
* The logs do not expose credentials or tokens.

The log is JSON by default. Add `DECPM_LOG_FORMAT=text` to the env file for the console format.

## Run a three-node mesh with Docker Compose

`development/docker-compose.yml` starts three nodes from the development Dockerfile. Each node connects to its own Canton participant.

1. Copy the three environment templates:

   ```bash
   for n in 1 2 3; do
     cp development/remote/participant-$n/.env.example development/remote/participant-$n/.env
   done
   ```
2. Edit each `.env` file. Set the network and the identity-provider values. For insecure mode on `devnet`, remove the `#` in front of `DECPM_INSECURE=true` instead. The template also holds `DECPM_KEYCLOAK_USERNAME`, `DECPM_KEYCLOAK_PASSWORD` and `P<n>_*` values. Only the integration test runner reads them.
3. Make the APIs of the three participants reachable on the Docker host:

   | Node            | Operator UI             | Noise port | Ledger API on the host | Admin API on the host |
   | --------------- | ----------------------- | ---------- | ---------------------- | --------------------- |
   | `participant-1` | `http://localhost:8081` | `9000`     | `5001`                 | `5002`                |
   | `participant-2` | `http://localhost:8082` | `9001`     | `5011`                 | `5012`                |
   | `participant-3` | `http://localhost:8083` | `9002`     | `5021`                 | `5022`                |
4. Start an SSH agent, then start the stack:

   ```bash
   eval "$(ssh-agent -s)"
   cd development
   docker compose up --build
   ```

   The Compose file forwards the SSH agent socket into the build. The build fails when `SSH_AUTH_SOCK` is unset. The agent needs no key.

Compose sets these values for each service:

* `DECPM_CANTON_ADMIN_HOST` and `DECPM_CANTON_LEDGER_HOST` become `host.docker.internal`.
* `DECPM_PUBLIC_ADDRESS` becomes the service name. Peers dial `participant-1`, `participant-2` and `participant-3` on the shared bridge network.
* `DECPM_DIR` becomes `/app`. Each node keeps its state in `development/remote/participant-<n>/data`.

The stack publishes only the HTTP ports. The metrics listener of each node stays inside its container. Treat the participant count and the ports as development values, not as a production architecture.

## Troubleshooting

| Symptom                                                               | Check                                                                                                      |
| --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| The development build fails on an Arm host                            | Add `--platform linux/amd64`.                                                                              |
| `docker compose` fails with `invalid empty ssh agent socket`          | Start an SSH agent so that `SSH_AUTH_SOCK` is set.                                                         |
| The container exits with `nothing accepts TCP connections there`      | The Admin API is not reachable from the container. Check `--add-host`, the host name and the port.         |
| The `-nonroot` container fails on a directory that a root image wrote | Run `sudo chown -R 65532:65532` on the host directory.                                                     |
| Every Canton call fails with `transport error` or `BrokenPipe`        | The TLS setting does not match the endpoint. Check `DECPM_CANTON_ADMIN_TLS` and `DECPM_CANTON_LEDGER_TLS`. |
| The UI loads, but every request returns `401`                         | Configure the identity provider, or use `--insecure` on `devnet`.                                          |
| Login redirects repeatedly                                            | Check the provider origin, redirect URI, client ID, realm or audience, and PKCE.                           |
| The peer identity changes after a restart                             | Check that the mount path matches `$DECPM_DIR/data` for the image.                                         |

## Technical sources

* [Release Dockerfile](https://github.com/DLC-link/decentralization-manager/blob/main/Dockerfile)
* [Release workflow](https://github.com/DLC-link/decentralization-manager/blob/main/.github/workflows/release.yml)
* [Development Dockerfile](https://github.com/DLC-link/decentralization-manager/blob/main/development/Dockerfile)
* [Development Compose file](https://github.com/DLC-link/decentralization-manager/blob/main/development/docker-compose.yml)
* [Participant `.env` template](https://github.com/DLC-link/decentralization-manager/blob/main/development/remote/participant-1/.env.example)
* [Startup and `.env` loading](https://github.com/DLC-link/decentralization-manager/blob/main/crates/decman/src/main.rs)
* [Operator guide](https://github.com/DLC-link/decentralization-manager/blob/main/USER_GUIDE.md)
* [Deployment guide](https://github.com/DLC-link/decentralization-manager/blob/main/docs/DEPLOYMENT_GUIDE.md)
* [Authentication middleware](https://github.com/DLC-link/decentralization-manager/blob/main/crates/decman/src/server/middleware/auth.rs)

## Next step

👉 **Form the development mesh:** [Connect peers](/decentralization-manager/get-started/connect-peers.md).


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.bitsafe.finance/decentralization-manager/get-started/run-with-docker.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
