# Docker
Source: https://dorkos.ai/docs/self-hosting/docker

Run DorkOS in a Docker container with persistent data, custom configuration, and Docker Compose



# Docker [#docker]

Run DorkOS as a container. The official image bundles Node.js, the DorkOS CLI, and the built client, so Docker is the only thing you need on the host.

## Quick Start [#quick-start]

```bash
docker run -d --name dorkos \
  -p 4242:4242 \
  -e ANTHROPIC_API_KEY=your-key-here \
  -v dorkos-data:/home/node/.dork \
  ghcr.io/dork-labs/dorkos:latest
```

This runs DorkOS in the background (`-d`), maps port 4242 on your machine to the container, passes your API key, and creates a named volume so your data survives container restarts.

Open [http://localhost:4242](http://localhost:4242).

## Image Versioning [#image-versioning]

Every release publishes the image with multiple tags:

| Tag           | Example                                | Description                                    |
| ------------- | -------------------------------------- | ---------------------------------------------- |
| `latest`      | `ghcr.io/dork-labs/dorkos:latest`      | Most recent non-prerelease version             |
| Exact version | `ghcr.io/dork-labs/dorkos:0.52.0`      | Pinned to a specific release                   |
| Minor track   | `ghcr.io/dork-labs/dorkos:0.52`        | Tracks the latest patch within a minor version |
| Commit SHA    | `ghcr.io/dork-labs/dorkos:sha-abc1234` | Pinned to a specific build commit              |

For production deployments, pin to an exact version:

```bash
docker pull ghcr.io/dork-labs/dorkos:0.52.0
```

The `latest` tag always points to the most recent non-prerelease version. Pre-release tags (e.g., `0.53.0-beta.1`) are available by exact version but do not update `latest`.

### Docker Hub mirror [#docker-hub-mirror]

The same image is mirrored to Docker Hub as `dorkai/dorkos`, with the same tags. GitHub Container Registry (the `ghcr.io` address above) stays the main home for the image. Docker Hub is a convenience copy, useful if your setup already pulls from there:

```bash
docker pull dorkai/dorkos:latest
```

## Multi-Platform Support [#multi-platform-support]

The image is built for both `linux/amd64` and `linux/arm64`. Docker automatically pulls the correct architecture for your machine:

* **Intel/AMD servers and desktops**: `linux/amd64`
* **Apple Silicon (M1/M2/M3) via Docker Desktop**: `linux/arm64`
* **ARM servers (AWS Graviton, Ampere)**: `linux/arm64`

You don't need to configure anything: `docker pull` selects the right platform automatically.

## Supply Chain Verification [#supply-chain-verification]

Every published image includes a [build provenance attestation](https://slsa.dev/) (a signed record of exactly which source commit built it) proving it came from the `dork-labs/dorkos` repository, not a fork. Verify any image with the GitHub CLI:

```bash
gh attestation verify oci://ghcr.io/dork-labs/dorkos:0.52.0 --owner dork-labs
```

A passing check confirms GitHub Actions built the image, not someone's laptop.

## Configuration [#configuration]

Pass environment variables with `-e` or an env file. DorkOS reads the same variables whether it's running in Docker or [installed directly](/docs/self-hosting/deployment).

<TypeTable
  type="{
  ANTHROPIC_API_KEY: { type: 'string', description: 'Claude API key (required)' },
  DORKOS_PORT: {
    type: 'number',
    description: 'Server port inside the container',
    default: &#x22;'4242'&#x22;,
  },
  DORKOS_HOST: {
    type: 'string',
    description: 'Bind address: always 0.0.0.0 in Docker (set automatically by the image)',
    default: &#x22;'0.0.0.0'&#x22;,
  },
  DORKOS_DEFAULT_CWD: {
    type: 'string',
    description:
      'Default working directory for new agent sessions. The image bakes no value, so sessions start in the container home.',
    default: &#x22;'/home/node'&#x22;,
  },
  DORKOS_BOUNDARY: {
    type: 'string',
    description: 'Directory boundary for file access',
    default: 'Home directory',
  },
  DORKOS_ALLOW_INSECURE_BIND: {
    type: 'boolean',
    description:
      'Set to true by the image so it can bind 0.0.0.0. Turn on login before exposing the container (see Securing Your Instance).',
    default: &#x22;'true'&#x22;,
  },
}"
/>

### Custom port [#custom-port]

Set `DORKOS_PORT` instead of using the `--port` flag. The container's built-in health check reads `DORKOS_PORT` to know which port to test, so setting the env var keeps DorkOS and its health check pointed at the same place. Match it with the container side of `-p host:container`:

```bash
docker run -d --name dorkos \
  -p 8080:8080 \
  -e ANTHROPIC_API_KEY=your-key-here \
  -e DORKOS_PORT=8080 \
  -v dorkos-data:/home/node/.dork \
  ghcr.io/dork-labs/dorkos:latest
```

<Callout type="warn">
  The `--port` flag still works, but it only tells DorkOS which port to listen on. The health check
  keeps checking port 4242, so `docker ps` would report the container unhealthy even though it's
  running fine. Use `DORKOS_PORT` instead so both stay in sync.
</Callout>

### Environment file [#environment-file]

Keep secrets out of your shell history:

```bash title=".env.dorkos"
ANTHROPIC_API_KEY=your-key-here
```

```bash
docker run -d --name dorkos \
  -p 4242:4242 \
  --env-file .env.dorkos \
  -v dorkos-data:/home/node/.dork \
  ghcr.io/dork-labs/dorkos:latest
```

## Persistent Data [#persistent-data]

DorkOS stores configuration, session transcripts, agent registrations, and the SQLite database in `/home/node/.dork` inside the container. Without a volume mount, all of this is lost when the container is removed. Mount a named volume or host directory to persist data across container restarts and upgrades.

<Callout type="warn">
  **Upgrading from 0.48 or earlier?** The container now runs as a non-root user, and its data
  directory moved from `/root/.dork` to `/home/node/.dork`. See [Upgrading from an older
  image](#upgrading-from-an-older-image) below before you pull the new version.
</Callout>

<Tabs items="[&#x22;Named Volume (Recommended)&#x22;, &#x22;Host Directory&#x22;]">
  <Tab value="Named Volume (Recommended)">
    ```bash
    docker run -d --name dorkos \
      -p 4242:4242 \
      -e ANTHROPIC_API_KEY=your-key-here \
      -v dorkos-data:/home/node/.dork \
      ghcr.io/dork-labs/dorkos:latest
    ```

    Docker manages the volume lifecycle. Inspect it with `docker volume inspect dorkos-data`.
  </Tab>

  <Tab value="Host Directory">
    ```bash
    mkdir -p ~/.dork
    docker run -d --name dorkos \
      -p 4242:4242 \
      -e ANTHROPIC_API_KEY=your-key-here \
      -v ~/.dork:/home/node/.dork \
      ghcr.io/dork-labs/dorkos:latest
    ```

    Useful for backup or inspection, since the data directory is directly accessible on the host.
  </Tab>
</Tabs>

### Upgrading from an older image [#upgrading-from-an-older-image]

Versions 0.48.0 and earlier ran the container as root, and stored data at `/root/.dork`. The image now runs as a regular, unprivileged user (`node`, uid 1000) instead of root, so a compromised agent or a bug can't touch the rest of the container as easily. That move means two things for anyone upgrading:

1. **The data path changed.** Change every `-v ...:/root/.dork` in your `docker run` command or Compose file to `-v ...:/home/node/.dork`.
2. **Existing files need new ownership.** Files created by the old root image are owned by root, and the new non-root user can't write to them until you fix that. Run this once, before starting the new image, against the same volume or host directory you already use:

```bash
docker run --rm -v dorkos-data:/data alpine chown -R 1000:1000 /data
```

Replace `dorkos-data` with your volume name, or with the host path (e.g. `~/.dork:/data`) if you mounted a directory instead of a named volume. After that, start the new image with the updated `/home/node/.dork` mount path as shown above.

### Mounting a project directory [#mounting-a-project-directory]

By default, agents running inside the container can only see the container's own filesystem. They have no access to your code on the host machine. To let agents read and write files in one of your projects, mount the project directory into the container and tell DorkOS to use it as the default working directory:

```bash
docker run -d --name dorkos \
  -p 4242:4242 \
  -e ANTHROPIC_API_KEY=your-key-here \
  -e DORKOS_DEFAULT_CWD=/workspace \
  -e DORKOS_BOUNDARY=/workspace \
  -v dorkos-data:/home/node/.dork \
  -v ~/projects/my-app:/workspace \
  ghcr.io/dork-labs/dorkos:latest
```

<Callout type="warn">
  Set `DORKOS_BOUNDARY` to the mounted directory to restrict agent file access. Without it, agents
  can access the entire container filesystem.
</Callout>

Since the container runs as an unprivileged user, files agents create in your mounted project are owned by uid 1000 instead of root, so they're usually already owned by your own user account on Linux hosts.

### Backing up your data [#backing-up-your-data]

Copy everything in the data volume into a single archive file you can store somewhere safe. Stop the container first so the SQLite database isn't being written to mid-copy:

```bash
docker stop dorkos
docker run --rm -v dorkos-data:/data -v "$(pwd)":/backup alpine \
  tar czf /backup/dorkos-backup.tgz -C /data .
docker start dorkos
```

This drops `dorkos-backup.tgz` in your current folder. To restore, extract that archive into a fresh volume the same way:

```bash
docker run --rm -v dorkos-data:/data -v "$(pwd)":/backup alpine \
  tar xzf /backup/dorkos-backup.tgz -C /data
```

## Docker Compose [#docker-compose]

The `docker run` commands above work fine for trying DorkOS out, but they're unwieldy to maintain. You end up with long shell commands that are easy to mistype and hard to version control. Docker Compose captures the full configuration in one file, so it's reproducible, easy to update, and simple to tear down with `docker compose down`.

The quickest way to start: download the ready-made file and run it.

```bash
curl -O https://dorkos.ai/compose.yml
export ANTHROPIC_API_KEY=your-key-here
docker compose up -d
```

`docker compose` finds `compose.yml` in the current directory automatically, no `-f` flag needed.

Want to see or customize it first? Here's what that file contains (minus a short comment header):

{/* Keep this block in sync with apps/site/public/compose.yml */}

```yaml title="compose.yml"
services:
  dorkos:
    image: ghcr.io/dork-labs/dorkos:latest
    container_name: dorkos
    restart: unless-stopped
    ports:
      - '4242:4242'
    environment:
      - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
    volumes:
      - dorkos-data:/home/node/.dork

volumes:
  dorkos-data:
```

<Callout type="info">
  No `healthcheck:` entry needed: the image already checks its own health every 30 seconds. Run
  `docker ps` and look at the `STATUS` column to see whether the container reports healthy or
  unhealthy.
</Callout>

### With a reverse proxy [#with-a-reverse-proxy]

<Callout type="error">
  **Turn on login before you point a domain at DorkOS.** The Docker image ships with login off, and it
  binds to every network address so port forwarding works. With login off, anyone who reaches the port
  gets full control: your sessions, your files, and your agents, spending your Claude quota. Create the
  owner account and require login first:

  ```bash
  docker exec -it dorkos dorkos auth enable
  ```

  This walks you through an email and password, then requires login from then on. You can also pass
  `--email` and `--password` to skip the prompts. Full details are in [Securing Your
  Instance](/docs/self-hosting/securing-your-instance).
</Callout>

The standalone Compose setup above serves DorkOS over plain HTTP on port 4242. That's fine for local use. But if you're exposing DorkOS over the internet (a VPS, a home server with a domain, or any machine other people will connect to), you need HTTPS. Without TLS, browsers block mixed content, credentials travel in the clear, and SSE streams (the live updates DorkOS uses to stream agent output) can be intercepted.

A reverse proxy sits in front of DorkOS, handles HTTPS, and forwards requests to the container. Caddy is the simplest option: it obtains and renews Let's Encrypt certificates automatically, so there's no manual cert management. It also handles the buffering settings SSE needs.

Add Caddy to the Compose file for automatic HTTPS:

```yaml title="compose.yml"
services:
  dorkos:
    image: ghcr.io/dork-labs/dorkos:latest
    container_name: dorkos
    restart: unless-stopped
    expose:
      - '4242'
    environment:
      - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
    volumes:
      - dorkos-data:/home/node/.dork

  caddy:
    image: caddy:2-alpine
    restart: unless-stopped
    ports:
      - '80:80'
      - '443:443'
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy-data:/data
      - caddy-config:/config

volumes:
  dorkos-data:
  caddy-data:
  caddy-config:
```

```text title="Caddyfile"
dorkos.example.com {
    reverse_proxy dorkos:4242 {
        flush_interval -1
    }
}
```

<Callout type="info">
  `flush_interval -1` disables response buffering, which is required for SSE streaming. See the
  [reverse proxy guide](/docs/self-hosting/reverse-proxy) for nginx configuration.
</Callout>

## Health Check [#health-check]

The image checks its own health automatically: every 30 seconds it asks itself whether the server is responding, and reports the result to Docker. Run `docker ps` and check the `STATUS` column for `healthy` or `unhealthy`, no setup required.

You can also check it yourself at any time:

```bash
curl http://localhost:4242/api/health
# { "status": "ok", "version": "0.52.0", "uptime": 12345 }
```

The health endpoint is unauthenticated and suitable for load balancer or container orchestrator use.

## Using MCP tools [#using-mcp-tools]

DorkOS exposes its tools to other AI clients at `/mcp`. Read-only tools work without any setup. Anything that changes state (running an agent, editing files) needs the instance's MCP token, or the call comes back with a `401` error that names where to find it.

Fetch the token from inside the container:

```bash
docker exec dorkos cat /home/node/.dork/mcp-local-token
```

Send it as a Bearer token (a key you attach to each request) on your calls to `/mcp`. Once you turn on login, per-user API keys replace this token. See the [MCP server guide](/docs/integrations/mcp-server) for the full setup.

## Building from Source [#building-from-source]

The official image from the container registry is the easiest path. Build from source if you need to run unreleased changes, test a fork, or include local modifications. One `Dockerfile` at the repository root covers both cases below.

<Steps>
  <Step>
    ### Build the CLI tarball [#build-the-cli-tarball]

    ```bash
    git clone https://github.com/dork-labs/dorkos.git
    cd dorkos
    pnpm install
    pnpm docker:build
    ```

    This builds the CLI package and creates the `dorkos:latest` image locally.
  </Step>

  <Step>
    ### Run the local image [#run-the-local-image]

    ```bash
    docker run -d --name dorkos \
      -p 4242:4242 \
      -e ANTHROPIC_API_KEY=your-key-here \
      -v dorkos-data:/home/node/.dork \
      dorkos:latest
    ```
  </Step>
</Steps>

### From published npm (no clone needed) [#from-published-npm-no-clone-needed]

If you want a custom Docker image but don't need local code changes, you can build one that installs a published version directly from npm, no tarball or local build required:

```bash
docker build \
  --build-arg INSTALL_MODE=npm \
  --build-arg DORKOS_VERSION=latest \
  -t dorkos:npm .
```

## Updating [#updating]

<Steps>
  <Step>
    ### Pull the latest image [#pull-the-latest-image]

    ```bash
    docker pull ghcr.io/dork-labs/dorkos:latest
    ```
  </Step>

  <Step>
    ### Recreate the container [#recreate-the-container]

    ```bash
    docker stop dorkos && docker rm dorkos
    docker run -d --name dorkos \
      -p 4242:4242 \
      -e ANTHROPIC_API_KEY=your-key-here \
      -v dorkos-data:/home/node/.dork \
      ghcr.io/dork-labs/dorkos:latest
    ```

    Or with Compose:

    ```bash
    docker compose pull
    docker compose up -d
    ```
  </Step>
</Steps>

Data in the `dorkos-data` volume persists across updates.

## Troubleshooting [#troubleshooting]

<Steps>
  <Step>
    ### Port forwarding not working [#port-forwarding-not-working]

    **Cause**: The server is binding to `localhost` inside the container.

    **Fix**: The official image sets `DORKOS_HOST=0.0.0.0` automatically. If you're using a custom image, ensure this variable is set.
  </Step>

  <Step>
    ### Pull fails with "unauthorized" or "denied" [#pull-fails-with-unauthorized-or-denied]

    **Cause**: A registry hiccup, a rate limit, or a stale login on your machine.

    **Fix**: Try the Docker Hub mirror, which carries the same image:

    ```bash
    docker pull dorkai/dorkos:latest
    ```

    If you were signed into the GitHub registry with an old token, sign out and pull again:

    ```bash
    docker logout ghcr.io
    docker pull ghcr.io/dork-labs/dorkos:latest
    ```
  </Step>

  <Step>
    ### Container exits immediately [#container-exits-immediately]

    Check the logs:

    ```bash
    docker logs dorkos
    ```

    Common causes: missing `ANTHROPIC_API_KEY`, port already in use on the host, or insufficient memory.
  </Step>

  <Step>
    ### Data lost after restart [#data-lost-after-restart]

    **Cause**: No volume mounted for `/home/node/.dork`.

    **Fix**: Add `-v dorkos-data:/home/node/.dork` to persist configuration and session data.
  </Step>

  <Step>
    ### Upgrading from a root-based image and the new container can't write its data [#upgrading-from-a-root-based-image-and-the-new-container-cant-write-its-data]

    **Cause**: Images from version 0.48.0 and earlier ran as root and stored data at `/root/.dork`. The
    new image runs as an unprivileged user instead, and that user can't write to files the old root
    image created.

    **Fix**: Fix ownership once, then switch to the new mount path:

    ```bash
    docker run --rm -v dorkos-data:/data alpine chown -R 1000:1000 /data
    ```

    Then start the new container with `-v dorkos-data:/home/node/.dork` (not `/root/.dork`). See
    [Upgrading from an older image](#upgrading-from-an-older-image) above for the full walkthrough.
  </Step>
</Steps>

## Next Steps [#next-steps]

<Cards>
  <Card title="Reverse Proxy" href="/docs/self-hosting/reverse-proxy">
    Configure nginx or Caddy for HTTPS, domain routing, and SSE compatibility.
  </Card>

  <Card title="Deployment" href="/docs/self-hosting/deployment">
    Environment variables, systemd, and production configuration.
  </Card>

  <Card title="Configuration" href="/docs/getting-started/configuration">
    Full configuration reference for ports, directories, and persistent settings.
  </Card>
</Cards>
