How to use a custom Docker image
By default, jailoc builds a base image from the Dockerfile embedded in the binary. This guide shows how to replace or extend that image at each level of customization. For the full resolution rules, see Image resolution reference.
Use a pre-built image directly
Set image in the workspace block to skip all build steps. Compose pulls the image from its registry at container startup.
[workspaces.myproject]
paths = ["~/projects/myproject"]
image = "myregistry.example.com/myteam/myapp:v1.2.3"
Warning
image cannot be combined with dockerfile or build_context in the same workspace block. Setting both is a validation error.
Set a default base image
Set image in [defaults] to use a pre-built image as the starting point for all workspaces. Workspaces that have their own dockerfile will use this as the BASE build argument; workspaces without a dockerfile will use it directly.
[defaults]
image = "ubuntu:22.04"
With this set, a workspace that adds its own dockerfile builds on top of ubuntu:22.04:
[workspaces.myproject]
paths = ["~/projects/myproject"]
dockerfile = "~/projects/myproject/overlay.Dockerfile"
A workspace without a dockerfile receives ubuntu:22.04 as-is.
Use a local Dockerfile as the base image
Set dockerfile in the global [base] section to an absolute path on your host. jailoc reads the file, builds it locally, and tags the result with a content-based hash (jailoc-base:preset-<hash>).
[base]
dockerfile = "/opt/myorg/base.Dockerfile"
Tilde paths work too:
[base]
dockerfile = "~/dockerfiles/base.Dockerfile"
Warning
If the file doesn't exist or the build fails, jailoc aborts. There is no fallback to the embedded image.
Use a remote Dockerfile URL as the base image
Set dockerfile in [base] to an HTTP(S) URL. jailoc downloads the file, builds it locally, and tags the result with a content-based hash.
[base]
dockerfile = "https://git.example.com/team/dockerfiles/-/raw/main/opencode.Dockerfile"
Warning
If the download fails or exceeds 1 MiB, jailoc aborts. The URL must be reachable at jailoc up time.
Add a workspace-specific layer
Set dockerfile in a [workspaces.<name>] block. jailoc builds this Dockerfile on top of whatever base image was resolved (from [base] settings, or defaults.image if set), passing the base tag as a build argument.
[workspaces.myproject]
paths = ["~/projects/myproject"]
dockerfile = "~/projects/myproject/overlay.Dockerfile"
The workspace Dockerfile must begin with:
ARG BASE
FROM ${BASE}
RUN apt-get update && apt-get install -y --no-install-recommends \
postgresql-client redis-tools \
&& rm -rf /var/lib/apt/lists/*
jailoc runs the build with --build-arg BASE=<resolved-base-tag> and tags the result as jailoc-myproject:<content-hash>.
HTTP URLs work here too:
[workspaces.myproject]
paths = ["~/projects/myproject"]
dockerfile = "https://git.example.com/team/dockerfiles/-/raw/main/myproject-overlay.Dockerfile"
Set an explicit build context for the workspace overlay
By default, the build context for a workspace overlay is the parent directory of the dockerfile (for local paths). Set build_context explicitly to control which files are available during the build.
[workspaces.myproject]
paths = ["~/projects/myproject"]
dockerfile = "~/projects/myproject/docker/overlay.Dockerfile"
build_context = "~/projects/myproject"
With this configuration, files from ~/projects/myproject are accessible via COPY instructions in the Dockerfile.
Write a good overlay Dockerfile
Keep layers small and cache-friendly
Order instructions from least to most volatile. Put package installs before copying project files, and copy files before running project-specific setup. Build tools and package managers should come last.
Combine related RUN commands into a single layer:
ARG BASE
FROM ${BASE}
RUN apt-get update && apt-get install -y --no-install-recommends \
postgresql-client \
redis-tools \
build-essential \
&& rm -rf /var/lib/apt/lists/*
For pip, suppress the cache directory:
RUN pip install --no-cache-dir -r requirements.txt
For npm, use --prefer-offline or clean the cache in the same layer:
RUN npm ci --prefer-offline && npm cache clean --force
For apk (Alpine-derived layers):
RUN apk add --no-cache curl jq
Use multi-stage builds for compiled tools
If you need a compiled binary, build it in a separate stage and copy only the output:
ARG BASE
FROM golang:1.24 AS builder
WORKDIR /src
COPY tool/ .
RUN CGO_ENABLED=0 go build -o /bin/mytool .
FROM ${BASE}
COPY --from=builder /bin/mytool /usr/local/bin/mytool
This keeps the final image free of compilers and intermediate artifacts.
What to avoid
Fatal: breaks the container entirely
These changes cause the container to fail at startup or make the agent unusable:
- Deleting
/usr/local/bin/entrypoint.sh— the compose template sets this as the container entrypoint; removing it prevents the container from starting. - Deleting
/home/agentor removing UID 1000 — the entrypoint drops privileges to UID 1000 and all agent tools run as this user; without it, startup fails. - Removing
iptables— the entrypoint uses iptables to enforce network isolation rules; without it, the container exits immediately. - Removing
setpriv— the entrypoint callssetprivto drop capabilities and switch to UID 1000; without it, the agent runs as root or the container fails to start.
Breaking: causes silent misbehaviour
These changes don't prevent the container from starting, but they break expected behaviour in non-obvious ways:
- Overriding
ENV PATHwithout including/home/agent/.local/binand/home/agent/.opencode/bin— the agent's local pip tools and opencode plugins won't be found. - Adding
VOLUMEon workspace mount paths — Docker volumes shadow bind mounts, so your project files won't appear inside the container.
Degraded: reduces functionality
- Removing
sudo— the entrypoint uses sudo to set up iptables rules before dropping privileges. Removing it degrades the network isolation setup.
For the full compatibility matrix, see Overlay compatibility reference.