Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Docker

All paths below are repo-relative (assets/Dockerfile, assets/compose.yaml, assets/omw.example.toml, assets/omw.example.env). The Dockerfile builds a minimal Alpine image straight from the GitHub release tarballs (the binaries are static musl, so there is nothing to compile):

# omw on Alpine. The release binaries are statically linked (musl), so they
# drop straight onto Alpine with no extra runtime dependencies besides
# ca-certificates and tzdata.
#
#   docker build \
#     --build-arg OMW_VERSION=0.1.0 \
#     --build-arg OMW_VARIANT=rhai \
#     --build-arg OMW_ARCH=x86_64-linux \
#     -t omw .
#
# Variants: `default` (wasm brains only), `rhai`, `js`. The tarball names are
# `omw-<arch>.tar.gz`, `omw-rhai-<arch>.tar.gz`, `omw-js-<arch>.tar.gz`.
ARG OMW_VERSION=0.1.0
ARG OMW_VARIANT=rhai
ARG OMW_ARCH=x86_64-linux

FROM alpine:3.21 AS fetch
ARG OMW_VERSION
ARG OMW_VARIANT
ARG OMW_ARCH
RUN apk add --no-cache ca-certificates \
  && if [ "${OMW_VARIANT}" = "default" ]; then TARBALL="omw-${OMW_ARCH}.tar.gz"; BIN="omw-${OMW_ARCH}"; \
     else TARBALL="omw-${OMW_VARIANT}-${OMW_ARCH}.tar.gz"; BIN="omw-${OMW_VARIANT}-${OMW_ARCH}"; fi \
  && wget -O "/tmp/${TARBALL}" \
    "https://github.com/haras-unicorn/omw/releases/download/v${OMW_VERSION}/${TARBALL}" \
  && tar -xzf "/tmp/${TARBALL}" -C /tmp \
  && mv "/tmp/${BIN}" /tmp/omw \
  && chmod +x /tmp/omw

FROM alpine:3.21
RUN apk add --no-cache ca-certificates tzdata \
  && adduser -D -H omw \
  && mkdir -p /var/lib/omw /etc/omw \
  && chown omw:omw /var/lib/omw
COPY --from=fetch /tmp/omw /usr/local/bin/omw

# Config, brains and the agent workspace. Mount your config at
# /etc/omw/omw.toml and the workspace at /var/lib/omw (the stateDir
# equivalent); see assets/omw.example.toml and assets/compose.yaml.
VOLUME /var/lib/omw
EXPOSE 8080
USER omw
ENTRYPOINT ["/usr/local/bin/omw", "loop", "--config", "/etc/omw/omw.toml"]
docker build \
  --build-arg OMW_VERSION=0.1.0 \
  --build-arg OMW_VARIANT=rhai \
  -t omw .
docker run -d --name omw --restart unless-stopped \
  --env-file omw.env \
  -e OMW__TUNABLES__ALLOW_UNLOCKED_SECRETS=true \
  -v ./omw.toml:/etc/omw/omw.toml:ro \
  -v omw-workspace:/var/lib/omw \
  -p 8080:8080 \
  omw

Secrets cannot mlock() inside containers (the container’s own RLIMIT_MEMLOCK is enforced regardless of the unit’s LimitMEMLOCK=), so the shipped example docker-compose.yaml sets OMW__TUNABLES__ALLOW_UNLOCKED_SECRETS=true — see tunables. Start from the example config and env. Secrets travel as OMW__-prefixed variables (--env-file or -e), never baked into the image. /var/lib/omw is the stateDir equivalent: mount a named volume or bind mount there for brains and the filesystem MCP workspace. Publish 8080 only when [endpoint] is configured (listen = "0.0.0.0:8080" inside containers).

Compose

The compose example wires it together, including an optional HTTP MCP server on the same network. The example config it mounts is:

# Example compose setup for omw. Builds the image straight from the
# release tarballs (see assets/Dockerfile); any git URL works as a build
# context, e.g. `context: https://github.com/haras-unicorn/omw.git`.
services:
  omw:
    build:
      context: https://github.com/haras-unicorn/omw.git
      dockerfile: assets/Dockerfile
      args:
        OMW_VERSION: "0.1.0"
        # default | rhai | js
        OMW_VARIANT: rhai
        # x86_64-linux | aarch64-linux
        OMW_ARCH: x86_64-linux
    env_file:
      - ./omw.env
    environment:
      # Secrets cannot mlock() inside containers (the container's own
      # RLIMIT_MEMLOCK wins over the unit's LimitMEMLOCK=), so ensure
      # that this doesn't make entire OMW fail.
      - OMW__TUNABLES__ALLOW_UNLOCKED_SECRETS=true
      # Layers over providers.openai.api_key in omw.toml; prefer env_file.
      - OMW__PROVIDERS__OPENAI__API_KEY=${OPENAI_API_KEY}
    volumes:
      # Config + brains (read-only) and the agent workspace (read-write,
      # the stateDir equivalent for filesystem MCP tooling).
      - ./omw.toml:/etc/omw/omw.toml:ro
      - omw-workspace:/var/lib/omw
    # Only needed with [endpoint] (listen = "0.0.0.0:8080").
    ports:
      - "8080:8080"
    restart: unless-stopped

  # Example HTTP MCP server on the same network. Point a tooling at it with
  # transport = "http" and url = "http://mcp:8000/mcp".
  # mcp:
  #   image: ghcr.io/modelcontextprotocol/server-everything
  #   networks:
  #     - default

volumes:
  omw-workspace:

Compose accepts a git URL as build.context, so you can build without cloning the repo; point dockerfile at the in-repo path and keep your omw.toml / omw.env beside your own compose file:

services:
  omw:
    build:
      context: https://github.com/haras-unicorn/omw.git
      dockerfile: assets/Dockerfile

MCP servers

  • HTTP transport (easy): run the server as another compose service and point the tooling at it (transport = "http", url = "http://mcp:8000/…"). No image changes needed.
  • stdio transport (node, uvx, bwrap wrappers, …): the server command must exist inside the omw image — a sidecar cannot help, since stdio means a subprocess. Extend the image:
FROM omw AS with-mcp
RUN apk add --no-cache nodejs
RUN npm install -g @modelcontextprotocol/server-filesystem

Then reference command = "server-filesystem" (or npx …) in the tooling config. The same applies to bwrap-wrapped commands: install bubblewrap in the image; NoNewPrivileges-style restrictions do not apply inside containers, and user namespaces work under the default Docker seccomp profile.