This file is for humans and coding agents alike. The long version, with the
recipe for each kind of change, lives in
rootfs/usr/share/devbox/skills/devbox/extending.md. Read it before touching
anything beyond a typo.
rootfs/ mirrors the image: rootfs/usr/local/bin/devbox in the repo becomes
/usr/local/bin/devbox in the container. The Dockerfile builds the image,
compose.yaml runs it, setup.sh installs a box without a clone and
scripts/ holds the host backup and restore. The user's home lives in
./data/home and survives every rebuild. That last point is the whole reason
for the rule below.
The image is rebuilt, the home is not. Anything that changes what an existing
home is expected to look like must come with a migration in
rootfs/usr/share/devbox/migrations/, or existing boxes silently stay on the
old layout. This applies whenever you:
- move, rename or remove a file or directory the box writes into the home
(
~/.config/dev-box/,~/.config/mise/,~/.claude/,~/.pi/,~/.omp/); - change the format or the meaning of a state file (
agent,packages,migrations, the seed references in~/.config/dev-box/seed/); - change a seeded file that the oldest boxes adopted without a reference copy,
so
dev-box-seedwill never update it on its own; - change a wrapper, a symlink or a default the home relies on at login.
Adding a brand new seeded file, a new command or a new package needs no migration: the seed and the entrypoint pick those up on the next start.
A migration catches existing homes up, it never carries the change itself. The
change goes in the seed, the Dockerfile or the script concerned first, so a
brand new home gets the target state straight away. The entrypoint never runs
a migration on a first start: it marks them all as played, since an empty home
has nothing to repair. A migration that is the only place where a change is
made is a bug, because new boxes will never see it.
To create one:
touch "rootfs/usr/share/devbox/migrations/$(date +%Y%m%d%H%M%S)-what-it-fixes.sh"The timestamp sets the order. The script runs once, as the user, at the next
start, and dev-box-migrate records it in ~/.config/dev-box/migrations.
Look at the existing migration in that directory for the shape. What it must
respect:
- idempotent: a failed run is retried, so it must survive running twice;
- harmless on a home that is already in the target state, or has nothing to
repair: say so and exit 0, since
devbox migratecan be run by hand at any time; - never overwrite something the user may have changed. Compare against the shipped version or a checksum of the versions you know were shipped, and say out loud when nothing was touched and why;
$HOMEis the box's home, it never runs as root;- English output, two spaces of indent;
- exit non-zero only when the repair genuinely failed.
| Change | Where |
|---|---|
| A package every box needs | Dockerfile, in the pacman -Syu list, with a comment saying why; check it exists on Arch Linux ARM too |
| A dev tool every box needs | rootfs/etc/devbox/mise-config.toml |
A new devbox command |
rootfs/usr/local/bin/dev-box-<name> with the # devbox: headers, summary.fr (French summary) included; nothing to register |
| A French text | only the devbox menu and the motd tips are translated: a # devbox:summary.fr= line under # devbox:summary=, and a `command |
| A config file shipped to the home | rootfs/etc/devbox/ plus a line in the SEEDS array of dev-box-seed and a row in the seed table of docs/customization.md |
A new devbox dev-env environment |
a rootfs/usr/share/devbox/dev-envs/<name>.sh with details, install and uninstall (is_installed when the mise config cannot tell, is_supported when it does not run everywhere), found on its own; mise use -g only (php from the image, browser, media and desktop through devbox pkg are the exceptions) |
| A change a user of the box notices | a line in the annotation of the next release tag (git tag -a v1.7): the workflow builds the image for amd64 and turns it into the GitHub release that devbox changelog and the next login show; arm64 is added by hand afterwards, gh workflow run build.yml --ref v1.7 (one multi-arch latest and v1.7) |
| Steps the user has to do by hand (an account, a download behind a login, a cable, a file on the host) | a rootfs/usr/share/devbox/help/<topic>.txt in the layout written at the top of rootfs/usr/share/devbox/lib/help.sh (title, numbered steps that say where they happen, sections, Sources), found by devbox help on its own; devbox help --check must pass; named in the table of docs/commands.md |
| A guide for the in-box skill | rootfs/usr/share/devbox/skills/devbox/*.md, listed in SKILL.md and in the skill table of docs/agents.md |
| A video for the course page | docs/videos/, read by the course through https://cdn.jsdelivr.net/gh/c4software/dev-box@main/docs/videos/<file>; .dockerignore keeps it out of the image. Replacing one keeps its name, then purge the CDN copy: curl https://purge.jsdelivr.net/gh/c4software/dev-box@main/docs/videos/<file> |
| User documentation | README.md for the essentials, docs/<topic>.md for the rest, linked from the Documentation list of the README |
Keep existing file names and their options untouched: the README, the pages
of docs/, setup.sh and entrypoint.sh call them by name.
- bash,
set -euo pipefail, noeval, shellcheck clean:shellcheck -e SC1091,SC2088 <files>in the box, or from the hostdocker run --rm -v "$PWD:/mnt:ro" koalaman/shellcheck:stable -e SC1091,SC2088 <files> - Comments, script output, README,
docs/and commit messages in English, plain prose, no arrows and no typographic dashes. - Commands say what they are doing and what to run next, and install nothing the user did not ask for. Running a command twice must not break anything.
- Update the documentation when a command, a variable or a seeded file changes:
the README only keeps the essentials (install, connecting, the main
commands, updating), the rest lives in
docs/, one page per topic. A command goes indocs/commands.md(and the page of its topic), a.envvariable or a seeded file indocs/customization.md, asetup.shquestion indocs/manual-install.md, a dev environment indocs/dev-envs.md.
Never test against your own dev-box container and never write into data/.
Build, then start a throwaway container on a scratch directory, as described
in the Testing without touching anything section of extending.md. A single
file can be copied into it with docker cp to iterate, but the real check is a
fresh build followed by devbox migrate --list and devbox seed --check.
Commit, push, and on the host rebuild the container from a fresh base:
docker compose build --pull --no-cache && docker compose up -ddocker compose up -d --build is enough when only rootfs/ changed.