open-docker-nest
v0.1.2
Published
Run OpenCode in Docker with host-project parity and non-root ownership.
Readme
open-docker-nest
Run OpenCode inside Docker with host-project parity, persistent OpenCode data, and non-root file ownership.
What this repository provides
- Canonical CLI command:
open-docker-nest(published frompackage.json) - A Docker image with
opencode,cache-ctrl, Playwright CLI + bundled Chromium support, Java 21 as the default JDK, Java 25 as an opt-in JDK, and a pinned Rust toolchain (1.95.0) includingrustfmt/clippyplus nativeccbuild baseline for Rust native linking - A
/workspacemount model for running against your host project - Persistent host-backed OpenCode config/state/share directories across runs
- Non-root execution via host UID/GID remapping
- Layered
open-docker-nest.jsonconfig (user + project) for validated extra container environment wiring
Prerequisites
- Docker
- Node.js + npm (only if you want to install the published CLI from npm)
- Bun
Install
Install the published CLI from npm:
npm install --global open-docker-nestOr run it without a global install:
npx open-docker-nest --helpThe default container image is published on Docker Hub as felixdock/open-docker-nest:latest.
If you want to pre-pull it explicitly:
docker pull felixdock/open-docker-nest:latestLocal development install from a repository clone (POSIX)
This repository includes non-interactive local-dev scripts that install/uninstall open-docker-nest in ~/.local/bin using a symlink to this clone's bin/open-docker-nest.js.
Install:
./install.shUninstall:
./uninstall.shBehavior and safety contract:
- Idempotent: running either script repeatedly is safe.
- Reversible: uninstall removes only the installed symlink.
- Overwrite protection: scripts refuse to overwrite/remove unrelated non-symlink files or unrelated symlink targets.
- Fail-fast diagnostics: missing prerequisites (for example,
HOME,readlink, or source script) fail with actionable errors. - PATH visibility: install warns when
~/.local/binis not present in your currentPATH.
If needed, add ~/.local/bin to PATH in your shell profile:
# POSIX sh / bash / zsh (for interactive shells)
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.profile
# fish
set -U fish_user_paths $HOME/.local/bin $fish_user_pathsBuild
docker build -t felixdock/open-docker-nest:latest .This command builds a local image in your Docker daemon using the same tag as the published default reference.
It does not pull or overwrite Docker Hub content; it only defines what felixdock/open-docker-nest:latest resolves to on your machine.
Local builds use the Dockerfile's checked-in default pinned toolchain arguments.
The Docker Hub publish workflow may rebuild with newer pinned versions of cache-ctrl, Bun, Playwright, Java 21, Java 25, Rust/rustup, Docker CLI, and Docker Buildx resolved at publish time and passed as Docker build args.
When that happens, the workflow uploads the resolved versions as CI artifacts for traceability.
To limit image bloat while preserving browser automation support, the image preinstalls Playwright Chromium only (instead of full multi-browser bundles).
Playwright runtime contract: the image remains Chromium-only, and /opt/google/chrome/chrome is provided as a compatibility launcher to the bundled Chromium binary.
PLAYWRIGHT_BROWSERS_PATH=/ms-playwright is set in-image, and bundled browser artifacts must remain readable by the remapped non-root runtime user.
The image and Docker Hub publish workflow support linux/amd64 only. Arm64 is unsupported.
Canonical default image: felixdock/open-docker-nest:latest.
For reproducible runs, replace latest with a specific version tag or image digest.
Usage
open-docker-nest update
open-docker-nest [--project <host-path>] [--image <image-ref>] [--java <21|25>] [--shell] [--host-docker] [--] [command ...args]open-docker-nestis the published command.--java <21|25>selects the default JDK inside the container for that run (default:21).--shellopens an interactive shell as useropencodewithHOME=/home/opencode.--host-dockerenables host Docker daemon access for the entire in-container session (explicit high-privilege mode).- With no command args and no
--shell, the wrapper still runsopencodeby default. - Commands provided after
--are passed through unchanged (-- <command> ...args). --repo-commandis removed; use--host-dockerfor session-wide host Docker access.- On implicit default-image runs (no
--image, noOPEN_DOCKER_NEST_IMAGE), the wrapper checks local availability and pullsfelixdock/open-docker-nest:latestonly when missing locally.
Explicit update command
Use the explicit update flow when you want to refresh both the published CLI and the default image:
open-docker-nest updateThis command runs:
npm install -g open-docker-nest@latestdocker pull felixdock/open-docker-nest:latest
Normal runtime startup does not perform remote freshness checks.
Host Docker mode (--host-docker)
Use this mode when tooling inside /workspace needs host Docker daemon access for a full OpenCode, shell, or pass-through session:
open-docker-nest --host-docker
open-docker-nest --shell --host-docker
open-docker-nest --host-docker -- docker versionScope and safety contract:
- Runs the session inside the container (not on the host), through the standard entrypoint as remapped non-root
opencode. - Mounts
/var/run/docker.sockonly for runs where--host-dockeris explicitly set. - Supports Linux/macOS only when a usable local Unix-socket Docker daemon is available at
/var/run/docker.sock. - Requires the active Docker context to be the default/local context.
- Supports best-effort Linux-in-WSL usage when invoked from Linux inside WSL and
/var/run/docker.sockis usable in that Linux environment. - Fails fast on native Windows host invocation for this mode, unsupported
DOCKER_HOSTendpoints, and missing/inaccessible Docker socket prerequisites. - Does not forward host Docker credentials/config (
~/.docker) in this slice. - Does not translate sibling-container bind-mount source paths from in-container
/workspace/...to host-visible paths in this slice.
Security note: this mode intentionally grants any process started in that flagged session control over the host Docker daemon for that run.
Non-goal: this is not a generic host-command bridge.
Rollback: stop using --host-docker and use existing default/--shell/normal pass-through modes.
Common examples
Run the default opencode command:
open-docker-nestRun any OpenCode command with the same pass-through shape:
open-docker-nest -- opencode --helpOpen an interactive shell in the container:
open-docker-nest --shellMount a different project directory:
open-docker-nest --project /path/to/project -- opencode --helpSwitch the in-container default JDK to Java 25 for one run:
open-docker-nest --java 25 -- /usr/bin/env bash -lc 'java -version && printf "%s\n" "$JAVA_HOME"'Windows support
Windows hosts support core flows only: default mode, --shell, and direct command pass-through. --host-docker and advanced local-dev modes for la-briguade and cache-ctrl remain Unix-like in this slice and are currently unsupported on native Windows hosts.
Mount and persistence model
- Project directory: host current directory by default →
/workspace - Config:
~/.config/opencode→/home/opencode/.config/opencode - State:
~/.local/state/opencode→/home/opencode/.local/state/opencode - Share:
~/.local/share/opencode→/home/opencode/.local/share/opencode
The wrapper creates missing persistence directories before docker run and fails fast with remediation if creation is not possible.
Non-root execution
The container starts as the opencode user and remaps runtime UID/GID from the invoking host user. Files created under /workspace are intended to remain owned by your host user rather than root.
Optional integrations
cache-ctrlis installed in the image and available at runtime.- If
~/.gitconfigexists, resolves to a regular file, and is readable, it is mounted read-only into the container at/home/opencode/.gitconfig. - If
~/la_briguadeexists and is readable, it is mounted into the container at/home/opencode/la_briguade. - Local la-briguade symlink workflows are supported through
LA_BRIGUADE_LOCAL_MODE(auto,force,off) and optionalLA_BRIGUADE_LOCAL_PATH; the authoritative source is~/.config/opencode/plugins/index.js, whose resolved target must be<la-briguade-repo>/dist/index.js. When active, the wrapper derives and mounts the local project root at<resolved ~/.config/opencode/plugins/index.js target>/../...
Project config (open-docker-nest.json)
The wrapper reads two config levels and merges them as: defaults < user < project.
- User config:
~/.config/open-docker-nest/open-docker-nest.json - Project config:
<project-root>/open-docker-nest.json
Both files use .json naming, and JSONC comments are supported.
Current supported field:
{
"extraContainerEnvironment": {
"OPENAI_API_KEY": "{env:OPENAI_API_KEY}",
"FEATURE_FLAG": "enabled"
}
}Behavior:
{env:ENV_VAR_NAME}placeholders are resolved from the host environment beforedocker run.- Placeholder syntax must match exactly
{env:ENV_VAR_NAME}(no surrounding whitespace). - Missing referenced host env vars fail fast with remediation.
- Runtime planning consumes only validated plain key/value pairs.
- For each configured key, docker args use
--env KEY(name only), and the value is supplied via the wrapper process environment at launch time. - This keeps secret values out of
docker runCLI arguments while still exposing them in-container.
Generate JSON Schema (off hot path):
bun run schema:generateOutput: schema/open-docker-nest.schema.json
More detail
- Operational workflow:
docs/docker-workflow.md - Behavior/spec source of truth:
openspec/specs/dockerized-open-docker-nest-workflow/spec.md
Published package and image
open-docker-nest is published for public use:
- npm package:
open-docker-nest - Docker Hub default image:
felixdock/open-docker-nest:latest - Package license:
MIT
Repository publication checks still run through prepublishOnly:
bun run typecheck
bun run test
bun run test:e2e
npm pack --dry-runNote: install.sh/uninstall.sh are local-dev helpers for repository clones and are not required for npm consumers.
