Main changes:
- Extract opencode.json generation from entrypoint-user.sh into a
standalone Python script (rootfs/usr/local/lib/opencode-devbox/
generate-config.py). Preserves the never-overwrite-existing-config
guarantee. Cuts entrypoint-user.sh from 176 to 97 lines.
- Install MemPalace via 'uv tool install' into an isolated venv at
/opt/uv-tools/mempalace/ with a /usr/local/bin/mempalace-mcp-server
wrapper, replacing the 'pip install --break-system-packages' escape
hatch. The wrapper is what generate-config.py references in the
auto-generated opencode.json. Also fix 'mempalace init' in
entrypoint-user.sh to use --yes so first-start initialization isn't
interactive (this used to hang or print prompts into the user's
terminal). Gated by INSTALL_MEMPALACE build arg (default true) so
users who don't need AI memory can shave ~300 MB.
- Sentinel-file pattern in entrypoint.sh volume-ownership loop: write
.devbox-owner after a successful chown -R, skip the recursive walk
on subsequent starts when the sentinel matches FINAL_UID:FINAL_GID.
Cuts multi-second startup costs to milliseconds on large volumes
(nvim plugins, palace data). UID changes still trigger a full chown.
- Float all GitHub/Gitea-hosted binary versions: gosu, fzf, git-lfs,
neovim, bat, eza, zoxide, uv, gitea-mcp now default to 'latest' and
resolve the newest upstream release at build time via the /releases/
latest redirect. Go (go.dev JSON feed) and oh-my-opencode-slim (npm
@latest) likewise. Intentional pins still in place: OPENCODE_VERSION,
NODE_VERSION=22, DEBIAN_VERSION=trixie-slim. Each *_VERSION ARG
accepts an explicit value to lock a specific version when needed.
- New scripts/smoke-test.sh verifies binary presence, opencode startup,
entrypoint user drop, generate-config idempotency, bun's presence-
per-variant, and image size against thresholds (2500 MB base, 3000
MB OMOS). Prints resolved component versions as its first step so
CI logs always record what got baked into a given image.
- New .gitea/workflows/validate.yml runs on push to main and PRs:
single-arch amd64 build, smoke test, DOCKER_HUB.md sync check. Tag-
triggered docker-publish.yml now smoke-tests each variant on amd64
before the full multi-arch push.
- scripts/generate-dockerhub-md.py auto-generates DOCKER_HUB.md from
README.md using explicit SECTION_RULES. --check mode fails CI when
the committed file is out of sync. Enforces the 25 kB Docker Hub
limit. Adding a new README section forces an explicit keep/drop/
replace decision.
- Remove dead INSTALL_PYTHON build arg (was a no-op since mempalace
added python3 unconditionally).
Python 3 has been unconditionally present since the Debian trixie
upgrade (e58962a, Apr 13) — python3 3.13 ships as a transitive
dependency of the trixie base image. python3-pip (e1029bb) and
python3-venv (3a7ec45) were later added to the base layer on Apr 23
so Mason could install Python-based LSPs (ruff, ansible-lint) into
venvs on nvim startup. MemPalace's pip install (b9c08c3) just
piggybacks on what was already there.
In other words, INSTALL_PYTHON=true has been a no-op reinstall of
already-installed packages for two weeks before MemPalace existed.
The flag is dead weight and the docs that advertise it as meaningful
are misleading. Remove it everywhere.
Users who want Python tooling should use the pre-installed uv/uvx.
Install gitea-mcp v1.1.0 (Go binary from gitea.com/gitea/gitea-mcp)
using the same multi-arch pattern as gosu/fzf/bat. Provides 50+ MCP
tools for Gitea API — repos, issues, PRs, releases, branches, wiki,
and Actions.
Disabled by default in auto-generated opencode.json (requires
GITEA_ACCESS_TOKEN and GITEA_HOST to be useful). Users enable it by
setting those env vars in .env and flipping enabled to true in their
opencode.json.
Env vars forwarded into the container via docker-compose.yml and
docker-compose.shared.yml environment blocks. Same {env:VAR} pattern
as the GitHub MCP server.
Docs updated: README.md (new Gitea MCP section with setup steps),
DOCKER_HUB.md (tools list), CHANGELOG.md (v1.14.28b entry).
The ONNX embedding model (~79 MB) downloads to ~/.cache/chroma/ on
first mempalace search. Without persistence it re-downloads on every
container recreation. Add a separate devbox-chroma-cache volume
rather than mixing it into the palace data volume — model cache is
disposable (delete and re-download), palace data is precious (back
up and migrate). Both volumes are commented out by default (opt-in).
Updated README.md storage section to explain the two-volume split
and the air-gapped pre-population path. Added chroma cache row to
DOCKER_HUB.md data storage table.
Install mempalace via pip in the Dockerfile. Provides 29 MCP tools
for semantic search over conversation history, knowledge graph
queries, agent diaries, and wing/room/drawer management. Everything
runs locally — no API keys, no data egress.
Integration:
- Dockerfile: pip install mempalace (with --break-system-packages
for Debian trixie PEP 668 compliance)
- entrypoint-user.sh: auto-initializes palace for /workspace on
first run (idempotent, skips if palace exists)
- entrypoint.sh: adds ~/.mempalace to the volume ownership-fix loop
- docker-compose.yml + shared: optional devbox-palace named volume
at ~/.mempalace (commented out by default — user opts in)
Users configure MCP integration by adding a mempalace server entry
to their opencode.json. No wrapper plugin needed — the upstream
Python MCP server is used directly.
Docs updated: README.md (new MemPalace section with setup, MCP
config, usage examples, storage details), DOCKER_HUB.md (data
storage table + tools list), CHANGELOG.md (unreleased entry).
Mason LSP installs and Lazy plugin cache live at ~/.local/share/nvim,
which was in the container's writable layer. Every --force-recreate
triggered a full re-download of all plugins and LSP servers on next
nvim launch — slow and wasteful.
Add devbox-nvim-data named volume in docker-compose.yml and
docker-compose.shared.yml, add to entrypoint ownership-fix loop,
update persistence tables in README.md and DOCKER_HUB.md.
Zoxide stores its database at ~/.local/share/zoxide/db.zo. Without a
named volume, the 'z <fragment>' jump targets are lost on every
'docker compose up --force-recreate'.
Add devbox-zoxide named volume in docker-compose.yml and
docker-compose.shared.yml, add ~/.local/share/zoxide to the
entrypoint ownership-fix loop per AGENTS.md convention, and update
the data-persistence tables in README.md and DOCKER_HUB.md.
The previous note scoped the single-file bind-mount staleness bug to
Docker Desktop only. It actually affects ALL platforms including native
Linux: Docker bind-mounts the inode, not the path. Editors that do
atomic save (vim, nvim, VS Code, sed -i) create a new inode via
rename(), leaving the container pinned to the old unlinked one. This
is a kernel limitation (moby/moby#15793, open since 2015, unfixable).
Rewrite both the README.md caveat and the docker-compose.yml inline
note to describe the real mechanism (inode replacement), name the
affected editors, note that append-only writes are safe, and link to
the upstream issue.
On Docker Desktop (macOS/Windows), single-file bind-mounts can
silently stop propagating host edits — the file gets materialized
onto the VM's ext4 disk and reused forever. This affects anyone who
uncomments the ~/.bash_aliases or ~/.inputrc mount lines.
Add a caveat note in README.md's 'Overriding the defaults / Option A'
section with the verification command and the directory-mount
workaround. Add a matching inline NOTE comment in docker-compose.yml
above the commented mount lines. Linux hosts are unaffected.
The shared-machine section in README.md still claimed named volumes
were isolated by directory-name prefixing alone, which was the bug
we just fixed. Rewrite to document both modes (own-account with
automatic $USER fallback, shared-account with explicit SIGNUM) and
explicitly note that the Docker daemon is system-wide — directory
name prefixing is NOT sufficient for volume isolation.
Previous behaviour (e4063b5) COPY'd .bash_aliases and .inputrc
directly into /home/developer/ during image build. That silently
shadowed any host bind-mount or in-container customization for users
upgrading from v1.14.19b — if you'd written your own .bash_aliases
and rebuilt the container, our baked version would overwrite it
without warning.
Ship the files to /etc/skel-devbox/ instead. The entrypoint copies
them to $HOME only if the target file does not already exist, so:
- Fresh containers get the defaults automatically (unchanged)
- Host bind-mounts win (they materialize before the entrypoint runs)
- Existing in-container customizations survive upgrades
- Defaults remain discoverable at /etc/skel-devbox/ for anyone who
wants to copy, diff, or reset back to upstream
Docs (README.md, DOCKER_HUB.md, deploy/README.md) describe the new
skel layout and the restore/diff commands.
Two changes that address a longstanding frustration: bash history is
lost on every container recreate, and the container's ~/.bashrc and
~/.inputrc are stock Debian (no history tuning, no prefix search on
arrow keys, no integrations).
Added a named volume 'devbox-shell-history' mounted at ~/.cache/bash
with HISTFILE pointing there; history now survives 'docker compose up
--force-recreate'. The volume is added to both docker-compose.yml and
docker-compose.shared.yml, and ~/.cache/bash is registered in the
entrypoint ownership-fix loop per the AGENTS.md convention.
Baked rootfs/home/developer/.bash_aliases (sourced automatically by
Debian's default ~/.bashrc) and rootfs/home/developer/.inputrc into
the image. They give new containers: 100k-entry timestamped dedup
history with per-prompt flush, Up/Down arrow prefix history search,
case-insensitive coloured completion, aliases that prefer eza and
bat when present, git shortcuts, interactive rm/mv/cp, zoxide and
fzf (via 'fzf --bash') integration, and a [devbox] prompt marker.
The fzf integration uses 'fzf --bash' because we install fzf from
GitHub releases, not apt — the apt-path key-bindings aren't present.
Users who prefer their host's own shell config can uncomment two
commented bind-mount lines in docker-compose.yml to shadow the
baked defaults.
Two related documentation fixes for users mounting ~/.config/opencode
from the host:
1. Gate oh-my-opencode-slim references (file and agents) to the OMOS
variant in the Custom opencode config sections and data persistence
tables. Base-variant users no longer see oh-my-opencode-slim.json
listed as if it were always present.
2. Add a portability note warning that host-absolute paths in
opencode.json (e.g. file:///usr/local/lib/node_modules/... or
file:///opt/homebrew/...) will not resolve inside the Linux
container, and to prefer bare package specifiers that work on
both macOS and Linux hosts.
For machines where multiple users share one OS account. Each user gets
isolated containers, config, and named volumes by running docker compose
from their own directory with a unique SIGNUM in .env.
Install rustup-init binary from Rust CDN. Users bootstrap Rust with
'rustup-init -y' — persists via devbox-rustup and devbox-cargo volumes.
Add JavaScript/TypeScript development docs (Node.js + npm in base, Bun in OMOS).
Install uv from GitHub releases (~23MB). Users can install Python with
'uv python install 3.12' — persists across restarts via devbox-uv volume.
Eliminates need for a separate Python image variant.
Mount ~/.config/opencode as a directory instead of individual files.
This persists all config changes (opencode.json, oh-my-opencode-slim.json,
skills) across container restarts. Add make to README architecture diagram.
Replace vim-tiny with neovim from GitHub releases (pinned, multi-arch).
Add bat, eza, zoxide from GitHub releases and tmux, htop, patch from apt.
Move tmux from OMOS-only to base image. Set EDITOR=nvim.
Add neovim config mount option to docker-compose and docs.
Integrate oh-my-opencode-slim as an opt-in feature via INSTALL_OMOS build arg.
A single build arg installs Bun, tmux, and the plugin; runtime activation is
controlled by ENABLE_OMOS and related env vars in the entrypoint.