OpenCode Development Container

container-world

This repository provides a Podman-based development container for running an AI coding agent in an isolated and reproducible environment.

The container includes common software development tools, Node.js, OpenCode, Python, Java, Maven, Git, Neovim, and other command-line utilities.

The host project directory and selected development caches/configuration directories are mounted into the container.

Requirements

  • Linux
  • Podman
  • Bash
  • A working internet connection when building the image
  • CA certificates, if access to internal HTTPS services requires custom CAs

Files

  • Dockerfile — builds the development image.
  • build.sh — builds, updates, removes, or prunes the image.
  • run.sh — starts the development container.
  • certs/README.md — rules for custom CA certificates.

Build the image

Run:

./build.sh

The default image is:

localhost/opencode-dev

The build uses the current host UID and GID for the dev user, so files created in mounted directories normally belong to the host user.

Build without cache

./build.sh --no-cache

Update the base image and Ubuntu packages

./build.sh --update

This is equivalent to --no-cache --pull=newer, so the Ubuntu base image is refreshed and every package layer is reinstalled.

Show the build command without running it

./build.sh --dry-run

Remove or prune images

./build.sh --remove   # delete the development image
./build.sh --prune    # delete dangling images left by previous builds

Both options exit immediately without building, and can be combined.

Install extra system packages

./build.sh --apt-packages="libpq-dev postgresql-client"

The list is forwarded to the image build as EXTRA_APT_PACKAGES and installed with apt-get install --no-install-recommends.

Show usage

./build.sh --help

Build arguments

The following environment variables can be used to customize the image:

UBUNTU_VERSION=26.04
NODE_VERSION=24.21.0
OPENCODE_VERSION=2.0.22
IMAGE_REPO=localhost/opencode-dev

For example:

NODE_VERSION=24.21.0 OPENCODE_VERSION=2.0.22 ./build.sh --no-cache

build.sh passes the versions above to podman build as --build-arg. The same values are declared as ARG defaults at the top of the Dockerfile, so keep both in sync when changing a version.

Two more build arguments are set automatically:

  • DEV_UID / DEV_GID — taken from id -u / id -g on the host.
  • EXTRA_APT_PACKAGES — taken from the --apt-packages option.

Custom CA certificates

Custom CA certificates must be placed in the repository certs/ directory before building the image. See certs/README.md for accepted formats and rules.

Git cannot track empty directories, so certs/README.md itself is the placeholder that keeps certs/ present in a fresh clone, and the build does not fail on a missing directory. That placeholder is listed in .dockerignore, so only real certificate files are copied into the image.

The Dockerfile copies this directory to:

/usr/local/share/ca-certificates

and runs:

update-ca-certificates

The resulting system CA bundle is also exposed through:

NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-certificates.crt

This allows both system tools and Node.js applications to trust the additional certificates.

Important: CA certificates need to be present when the image is built. The runtime container does not install certificates from the host. After adding or removing a certificate, rebuild with ./build.sh --no-cache --update.

Run the container

Run:

./run.sh

By default, the host directory is:

$HOME/opencode-dev

and projects are stored in:

$HOME/opencode-dev/projects

run.sh creates every directory it mounts, so it can be run on a clean checkout. If the image does not exist yet, the script exits with an error asking you to build it first.

The container starts in /workspace, which is the mounted projects/ directory. Open the project you want to work on inside the shell, or mount additional host directories with --mount.

Mounted directories

The following host directories are mounted into the container:

Host Container Purpose
projects/ /workspace Source code
opencode-config/ /home/dev/.config/opencode OpenCode configuration
opencode-data/ /home/dev/.local/share/opencode OpenCode persistent data
opencode-cache/ /home/dev/.cache/opencode OpenCode cache
m2/ /home/dev/.m2 Maven cache
npm-cache/ /home/dev/.npm npm cache
pip-cache/ /home/dev/.cache/pip pip cache
$HOME/.gitconfig /home/dev/.gitconfig Git configuration

All mounted development directories are created automatically by run.sh.

Additional mounts

Additional host directories can be mounted with --mount.

Example:

./run.sh --mount "$HOME/other:/extra"

Read-only mounts are also supported:

./run.sh --mount "$HOME/data:/data:ro"

Multiple mounts can be specified:

./run.sh \
  --mount "$HOME/data:/data:ro" \
  --mount "$HOME/tools:/tools:rw"

--mount is the only option accepted by run.sh; any other argument stops the script with an error.

Environment variables

The main runtime paths can be customized:

OPENCODE_DEV_HOME
PROJECT_DIR
OPENCODE_CONFIG
OPENCODE_SHARE_DATA
OPENCODE_CACHE
M2_FOLDER
NPM_CACHE
PIP_CACHE
IMAGE_REPO

IMAGE_REPO is read by both scripts, so it selects the same image for build.sh and run.sh.

Example:

OPENCODE_DEV_HOME="$HOME/my-opencode" ./run.sh

Git configuration

The host Git configuration is mounted read-only:

$HOME/.gitconfig -> /home/dev/.gitconfig

If the file does not exist, the script prints a warning and Git uses the container defaults.

SSH configuration and SSH agent forwarding are not mounted. If they are needed, pass them explicitly:

./run.sh --mount "$HOME/.ssh:/home/dev/.ssh:ro"

Security

The container runs with several restrictions:

  • Runs as the dev user with host UID/GID (--user dev --userns=keep-id).
  • Drops all Linux capabilities (--cap-drop=ALL).
  • Uses no-new-privileges.
  • Uses the host timezone (--tz=local).
  • Uses a temporary, named container (--name opencode-dev --rm).
  • Does not run as root.
  • Uses the default Podman network.

The project directory is mounted read/write because the AI agent needs to create and modify source files. The host Git configuration is mounted read-only.

Locale

The image generates the en_US.UTF-8 locale during the build:

echo 'en_US.UTF-8 UTF-8' > /etc/locale.gen && locale-gen

The host timezone is applied at runtime through --tz=local. The host LANG/LC_ALL values are not forwarded; export them inside the container if a different locale is needed.

Typical workflow

Build the image:

./build.sh

Start the development environment:

./run.sh

Inside the container, the projects directory is available under:

/workspace

Move into the project you want to work on, then start OpenCode normally:

opencode

The container is removed when the shell exits, while source code, OpenCode data, configuration, and development caches remain on the host.

Customization

Image versions are defined at the top of the Dockerfile:

ARG UBUNTU_VERSION=26.04
ARG NODE_VERSION=24.21.0
ARG OPENCODE_VERSION=2.0.22

Runtime paths, mounts, and container options are defined in run.sh, while build.sh controls how the image is built and maintained.

This separation allows the image to contain the stable development environment while projects, configuration, caches, and other working data remain outside the container.

S
Description
Opencode dev container
Readme 306 KiB
Languages
Shell 55.8%
Dockerfile 44.2%