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.