Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c182ada0dd | |||
| b9657415c4 | |||
| b37740bcce | |||
| 3982e9f18c | |||
| 4d0c270196 | |||
| aed5ff106b | |||
| 425d53cb57 | |||
| 60208b2203 | |||
| d65f8cc077 | |||
| 4560702550 |
+9
-3
@@ -1,7 +1,13 @@
|
||||
# ── Shared machine setup ─────────────────────────────────────────────
|
||||
# Your corporate signum / username (REQUIRED)
|
||||
# This isolates your container, config, and data from other users.
|
||||
SIGNUM=your-signum-here
|
||||
# SIGNUM isolates your container name and named volumes from other users.
|
||||
#
|
||||
# Own-account mode (each user has their own OS login):
|
||||
# Leave SIGNUM commented out — it defaults to your OS username ($USER).
|
||||
# SIGNUM=
|
||||
#
|
||||
# Shared-account mode (everyone logs in as the same OS user):
|
||||
# Uncomment and set to your unique identifier.
|
||||
# SIGNUM=your-signum-here
|
||||
|
||||
# ── Provider ─────────────────────────────────────────────────────────
|
||||
OPENCODE_PROVIDER=amazon-bedrock
|
||||
|
||||
@@ -228,6 +228,7 @@ Understanding what survives container restarts and what doesn't:
|
||||
| `/home/developer/.local/share/opencode` | Named volume (if configured) | ✅ Yes — Docker volume | Session history, memory, auth tokens |
|
||||
| `/home/developer/.local/state/opencode` | Named volume (if configured) | ✅ Yes — Docker volume | TUI settings (theme, toggles) |
|
||||
| `/home/developer/.cache/bash` | Named volume `devbox-shell-history` | ✅ Yes — Docker volume | Bash history (`$HISTFILE`) — survives container recreate |
|
||||
| `/home/developer/.local/share/zoxide` | Named volume `devbox-zoxide` | ✅ Yes — Docker volume | Zoxide directory history (`z <fragment>` jump targets) |
|
||||
| `/home/developer/.local/share/uv` | Named volume (if configured) | ✅ Yes — Docker volume | Python installs, uv tool installs |
|
||||
| `/home/developer/.rustup` | Named volume (if configured) | ✅ Yes — Docker volume | Rust toolchains |
|
||||
| `/home/developer/.cargo` | Named volume (if configured) | ✅ Yes — Docker volume | Cargo binaries, registry cache |
|
||||
@@ -547,6 +548,10 @@ ping all agents
|
||||
|
||||
All six agents should respond if your provider authentication is working.
|
||||
|
||||
## Multi-User Setup
|
||||
|
||||
This guide covers single-user setup. For running multiple opencode-devbox instances in parallel — whether each user has their own OS account or everyone shares one login — see the [Multi-user setup section](https://gitea.jordbo.se/joakimp/opencode-devbox#multi-user-setup) in the source repository. It covers volume isolation, the `docker-compose.shared.yml` layout, and the `SIGNUM` / `$USER` auto-detection mechanism.
|
||||
|
||||
## Source
|
||||
|
||||
Build from source or contribute: [opencode-devbox on Gitea](https://gitea.jordbo.se/joakimp/opencode-devbox)
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ ARG DEBIAN_VERSION=trixie-slim
|
||||
FROM debian:${DEBIAN_VERSION} AS base
|
||||
|
||||
ARG TARGETARCH
|
||||
ARG OPENCODE_VERSION=1.14.20
|
||||
ARG OPENCODE_VERSION=1.14.21
|
||||
|
||||
LABEL maintainer="joakimp"
|
||||
LABEL description="Portable opencode developer container"
|
||||
|
||||
@@ -273,11 +273,17 @@ volumes:
|
||||
- devbox-vscode:/home/developer/.vscode-server
|
||||
```
|
||||
|
||||
### Shared machine setup (multiple users, single OS account)
|
||||
### Multi-user setup
|
||||
|
||||
For machines where multiple users share one OS account (e.g. a common `garage` user), a separate compose file isolates each user's config and data using a `SIGNUM` variable.
|
||||
The shared-machine compose file (`docker-compose.shared.yml`) supports two modes:
|
||||
|
||||
Each user creates their own directory and setup:
|
||||
**Own-account mode** (each user has their own OS login — the common case):
|
||||
Leave `SIGNUM` unset in `.env`. The project name defaults to `devbox-$USER`, so each OS user automatically gets isolated container names and named volumes with zero configuration.
|
||||
|
||||
**Shared-account mode** (everyone logs in as the same OS user, e.g. `garage`):
|
||||
Each user sets `SIGNUM=<unique-id>` in `.env` to get isolation.
|
||||
|
||||
Setup per user:
|
||||
|
||||
```bash
|
||||
# Replace <signum> with your username/identifier
|
||||
@@ -291,17 +297,17 @@ cp /path/to/opencode-devbox/.env.shared.example .env
|
||||
# Create per-user config directory
|
||||
mkdir -p ~/<signum>/.config/opencode
|
||||
|
||||
# Edit .env with your signum, provider, keys, etc.
|
||||
# Edit .env — set SIGNUM only if you're in shared-account mode
|
||||
vim .env
|
||||
|
||||
# Start
|
||||
docker compose up -d
|
||||
docker compose exec -u developer devbox-<signum> opencode
|
||||
docker compose exec -u developer devbox opencode
|
||||
```
|
||||
|
||||
Each user's container, config, and named volumes are fully isolated:
|
||||
- Container name: `devbox-<signum>` (no collisions)
|
||||
- Named volumes: prefixed with the project directory name (automatic per-user isolation)
|
||||
- Container name: `devbox-<signum>` (or `devbox-$USER` in own-account mode)
|
||||
- Named volumes: prefixed with the project name (`devbox-<signum>_devbox-data`, etc.) — the Docker daemon is system-wide, so directory-name prefixing alone is NOT sufficient for isolation
|
||||
- Opencode config: `~/<signum>/.config/opencode/` (per-user settings, OMOS config, etc.)
|
||||
|
||||
See `docker-compose.shared.yml` and `.env.shared.example` for the full configuration.
|
||||
@@ -460,6 +466,8 @@ Defaults you get out of the box:
|
||||
- ~/.inputrc:/home/developer/.inputrc:ro
|
||||
```
|
||||
|
||||
> **Single-file bind-mount caveat (all platforms):** Docker bind-mounts the file's **inode**, not its path. When editors like vim, nvim, VS Code, or `sed -i` save a file, they write to a temp file and `rename()` it over the original — creating a new inode. The container stays pinned to the old (now unlinked) inode and never sees the update. This is a kernel limitation ([Docker #15793](https://github.com/moby/moby/issues/15793)), not fixable by Docker. Append-only writes (`echo "alias foo=bar" >> file`) are safe because they modify the same inode. **Workaround:** mount the parent directory instead of the single file (e.g. `~/.config/devbox-shell:/home/developer/.config/devbox-shell:ro`) and source files from there.
|
||||
|
||||
**Option B — customize inside the container.** Just edit `~/.bash_aliases` or `~/.inputrc` as normal. Pair this with a bind-mount or named volume on the home dir if you want the edits to survive container recreation.
|
||||
|
||||
### Restoring or diffing defaults
|
||||
@@ -540,6 +548,7 @@ Container (Debian trixie)
|
||||
| `/home/developer/.local/share/opencode` | Named volume `devbox-data` | ✅ Yes | Session history, memory |
|
||||
| `/home/developer/.local/state/opencode` | Named volume `devbox-state` | ✅ Yes | TUI settings (theme, toggles) |
|
||||
| `/home/developer/.cache/bash` | Named volume `devbox-shell-history` | ✅ Yes | Bash history (`$HISTFILE`), survives container recreate |
|
||||
| `/home/developer/.local/share/zoxide` | Named volume `devbox-zoxide` | ✅ Yes | Zoxide directory history (`z <fragment>` jump targets) |
|
||||
| `/home/developer/.local/share/uv` | Named volume `devbox-uv` (if configured) | ✅ Yes | Python installs, uv tool installs |
|
||||
| `/home/developer/.rustup` | Named volume `devbox-rustup` (if configured) | ✅ Yes | Rust toolchains |
|
||||
| `/home/developer/.cargo` | Named volume `devbox-cargo` (if configured) | ✅ Yes | Cargo binaries, registry cache |
|
||||
|
||||
@@ -198,6 +198,34 @@ After editing `docker-compose.yml` on the VM to uncomment the bind mounts you ne
|
||||
|
||||
The script reads `docker-compose.yml` on the remote VM, detects which bind mounts are active, and syncs only those directories from your local machine. It also creates the remote directories if they don't exist.
|
||||
|
||||
### Upgrading an existing VM to a new release
|
||||
|
||||
Each tagged release may add new named volumes or bind-mount lines to `docker-compose.yml`. Pulling a new image via `docker compose pull` grabs the new container behaviour, but compose files on the VM are user-owned and never touched by the image — you have to reconcile them yourself when upgrading across versions.
|
||||
|
||||
**Symptom of a missed reconcile:** a new feature quietly doesn't work even though the image is correct. Example from v1.14.19c → v1.14.20: bash history persistence requires the `devbox-shell-history` named volume mounted at `/home/developer/.cache/bash`. The v1.14.20 image writes history to that path either way, but without the volume mount on the VM, writes land in the container's writable layer and vanish on every `--force-recreate`.
|
||||
|
||||
**Upgrade ritual:**
|
||||
|
||||
```bash
|
||||
# On the VM, before recreating the container:
|
||||
cd ~/opencode-devbox
|
||||
cp docker-compose.yml docker-compose.yml.bak-$(date +%Y%m%d-%H%M%S)
|
||||
|
||||
# Compare against the repo version to see what's new:
|
||||
# (from your local checkout)
|
||||
scp devbox-affection:~/opencode-devbox/docker-compose.yml /tmp/vm-compose.yml
|
||||
diff -u /tmp/vm-compose.yml ~/src/src_local/opencode-devbox/docker-compose.yml
|
||||
```
|
||||
|
||||
For each new `volumes:` entry or mount line in the repo version that isn't in your VM's file, add it manually — preserving any local customizations you've made (image variant, read/write flags on bind mounts, etc.). Then:
|
||||
|
||||
```bash
|
||||
docker compose config >/dev/null # verify YAML still parses
|
||||
docker compose up -d --force-recreate
|
||||
```
|
||||
|
||||
If you maintain the VM's compose file with no local changes, `scp` the repo version over wholesale. If you have customizations (the common case), do the diff-and-merge by hand.
|
||||
|
||||
### Shell defaults inside the container
|
||||
|
||||
The image ships baked `.bash_aliases` and `.inputrc` in `/etc/skel-devbox/` — quality-of-life defaults (prefix history search on Up/Down arrows, persistent history across container recreates via the `devbox-shell-history` named volume, `[devbox]` prompt marker, sensible aliases). On first container start the entrypoint copies them to `/home/developer/` **only if the target file does not already exist**.
|
||||
|
||||
@@ -12,14 +12,24 @@
|
||||
# 5. mkdir -p ~/<signum>/.config/opencode
|
||||
# 6. docker compose up -d
|
||||
#
|
||||
# Named volumes are automatically isolated per user because Docker Compose
|
||||
# prefixes them with the project directory name (e.g. opencode-devbox_devbox-data).
|
||||
# Since each user runs from ~/<signum>/opencode-devbox/, volumes don't collide.
|
||||
# Volume isolation: the top-level 'name:' field derives a unique project
|
||||
# name per user, which Docker Compose uses as the prefix for all named
|
||||
# volumes. Without this, two users whose compose file lives in a directory
|
||||
# with the same basename would share volumes — the Docker daemon is
|
||||
# system-wide and doesn't scope by OS user.
|
||||
#
|
||||
# Two modes:
|
||||
# Own-account mode (each user has their own OS login):
|
||||
# Leave SIGNUM unset in .env — it defaults to $USER automatically.
|
||||
# Shared-account mode (everyone logs in as the same OS user):
|
||||
# Set SIGNUM=<unique-id> in .env so each person gets isolated volumes.
|
||||
|
||||
name: devbox-${SIGNUM:-${USER}}
|
||||
|
||||
services:
|
||||
devbox:
|
||||
image: joakimp/opencode-devbox:latest
|
||||
container_name: devbox-${SIGNUM:?Set SIGNUM in .env}
|
||||
container_name: devbox-${SIGNUM:-${USER}}
|
||||
stdin_open: true
|
||||
tty: true
|
||||
env_file:
|
||||
@@ -42,6 +52,9 @@ services:
|
||||
# Persist bash history across container recreations
|
||||
- devbox-shell-history:/home/developer/.cache/bash
|
||||
|
||||
# Persist zoxide directory history ('z <fragment>' to jump)
|
||||
- devbox-zoxide:/home/developer/.local/share/zoxide
|
||||
|
||||
# Persist uv data (Python installs)
|
||||
- devbox-uv:/home/developer/.local/share/uv
|
||||
|
||||
@@ -51,4 +64,5 @@ services:
|
||||
volumes:
|
||||
devbox-data:
|
||||
devbox-shell-history:
|
||||
devbox-zoxide:
|
||||
devbox-uv:
|
||||
|
||||
@@ -8,6 +8,11 @@
|
||||
# Or for interactive one-shot:
|
||||
# docker compose run --rm devbox
|
||||
|
||||
# Pin the project name so named volumes survive directory renames.
|
||||
# Without this, Docker Compose derives the project name from the
|
||||
# directory basename — renaming the dir orphans all existing volumes.
|
||||
name: opencode-devbox
|
||||
|
||||
services:
|
||||
devbox:
|
||||
image: joakimp/opencode-devbox:latest
|
||||
@@ -56,9 +61,19 @@ services:
|
||||
# Without this, ~/.bash_history is lost on 'docker compose up --force-recreate'.
|
||||
- devbox-shell-history:/home/developer/.cache/bash
|
||||
|
||||
# Persist zoxide directory history ('z <fragment>' to jump).
|
||||
- devbox-zoxide:/home/developer/.local/share/zoxide
|
||||
|
||||
# Optional: override baked shell defaults with your host's rc files.
|
||||
# The image ships sensible defaults (history tuning, prefix-search on
|
||||
# Up/Down arrows, fzf/zoxide integration). Uncomment to use your own:
|
||||
#
|
||||
# NOTE: Single-file bind-mounts break when editors use atomic save
|
||||
# (vim, VS Code, sed -i write a temp file then rename() over the
|
||||
# original, creating a new inode the container never sees). This is a
|
||||
# kernel limitation, not Docker-specific. If host edits stop appearing
|
||||
# in the container, mount the parent directory instead — see the
|
||||
# "Shell defaults" section in README.md.
|
||||
# - ~/.bash_aliases:/home/developer/.bash_aliases:ro
|
||||
# - ~/.inputrc:/home/developer/.inputrc:ro
|
||||
|
||||
@@ -81,6 +96,7 @@ volumes:
|
||||
devbox-data:
|
||||
devbox-state:
|
||||
devbox-shell-history:
|
||||
devbox-zoxide:
|
||||
devbox-uv:
|
||||
# devbox-rustup:
|
||||
# devbox-cargo:
|
||||
|
||||
@@ -77,6 +77,7 @@ for dir in \
|
||||
/home/"$USER_NAME"/.local/share/opencode \
|
||||
/home/"$USER_NAME"/.local/state/opencode \
|
||||
/home/"$USER_NAME"/.local/share/uv \
|
||||
/home/"$USER_NAME"/.local/share/zoxide \
|
||||
/home/"$USER_NAME"/.cache/bash \
|
||||
/home/"$USER_NAME"/.rustup \
|
||||
/home/"$USER_NAME"/.cargo \
|
||||
|
||||
Reference in New Issue
Block a user