A development container image that installs Jetify Devbox with a non-root user and a single-user Nix installation.
amd64 and arm64 hosts.Pull and run the published image using its Docker Hub namespace:
docker pull geoffh1977/jetify-devbox:latest
docker run --rm -it geoffh1977/jetify-devbox:latest
Mount a project directory when working on a repository:
docker run --rm -it -v "$PWD:/Project" geoffh1977/jetify-devbox:latest
By default the container runs as devbox with UID and GID 1000. Set both
DEVBOX_UID and DEVBOX_GID to run as a different numeric identity, for
example when a mounted home directory or Nix volume belongs to your host user:
docker run --rm -it \
-e DEVBOX_UID=502 \
-e DEVBOX_GID=20 \
-v devbox-nix:/nix \
-v devbox-home:/home/devbox \
geoffh1977/jetify-devbox:latest
Both variables are required together and must be decimal integers from 1
through 60000.
The image refuses a UID already assigned to another account. If the requested
GID already exists, devbox safely uses that group; otherwise the entrypoint
creates a dedicated group. At startup it repairs ownership of each fixed
managed root (/home/devbox and /nix) and its non-mounted content. The base
roots are ownership-managed, but every descendant mount is pruned before
ownership is checked or changed. Therefore arbitrary host bind mounts are never
ownership-managed—regardless of their source path, target path, or mountinfo
shape. /Project is never touched.
Named-volume descendants are also deliberately left untouched at runtime.
Docker mount metadata cannot safely distinguish a named volume from a bind mount
whose source was chosen to resemble a volume backing path. The entrypoint never
uses a Docker socket, container ID, hostname, cgroup data, or environment path
to override that rule. This keeps Docker Desktop and host bind mounts below
/home/devbox or /nix outside its recursive ownership traversal.
If named-volume state was previously created as root, run a separate one-shot
initializer before starting the devbox service. It mounts only the named
volumes selected in the Compose declaration at neutral paths and changes only
those paths. It does not mount the Docker socket or any host bind path.
Use this generic Compose pattern. The initializer runs the image's
chown-volumes helper as root; Compose substitutes DEVBOX_UID and
DEVBOX_GID (or 1000 when they are unset):
services:
init-devbox-volumes:
image: geoffh1977/jetify-devbox:latest
user: "0:0"
entrypoint: ["/usr/local/bin/chown-volumes", "-u", "${DEVBOX_UID:-1000}", "-g", "${DEVBOX_GID:-1000}", "-R", "/managed"]
volumes:
- devbox-state:/managed/.devbox
- vscode-state:/managed/.vscode-server
devbox:
environment:
DEVBOX_UID: "${DEVBOX_UID:-1000}"
DEVBOX_GID: "${DEVBOX_GID:-1000}"
volumes:
- devbox-state:/home/devbox/.devbox
- vscode-state:/home/devbox/.vscode-server
volumes:
devbox-state:
vscode-state:
Run docker compose run --rm init-devbox-volumes once, then start devbox
normally. Do not add host bind mounts to the initializer; migration scope is
the exact list of named-volume declarations above. For a direct equivalent:
docker run --rm --user 0:0 --entrypoint /usr/local/bin/chown-volumes \
-v devbox-state:/managed/.devbox \
-v vscode-state:/managed/.vscode-server \
geoffh1977/jetify-devbox:latest \
-u 502 -g 20 -R /managed
Use -t (or --include-root) only when the named volume is mounted directly
at /managed; by default the helper selects immediate real directories below
/managed and ignores symlinks. It skips targets already owned by the requested
UID:GID, changes only root:root targets, and exits nonzero rather than changing
any other ownership.
The runtime entrypoint continues to repair the base managed roots and their
non-mounted content. It prunes every descendant mount before checking or
changing ownership, so initialized named volumes are usable by the mapped user
while nested host bind mounts remain exactly as supplied. Base roots may be
read-only only when they already belong to the mapped UID:GID; otherwise startup
exits with an actionable error rather than continuing with a broken single-user
Nix or home directory. VOLUME_MOUNTS is not used for ownership changes:
arbitrary environment-provided paths are intentionally excluded.
Use the image in a .devcontainer/devcontainer.json file, replacing the namespace with the published Docker Hub namespace:
{
"image": "geoffh1977/jetify-devbox:latest",
"workspaceFolder": "/Project"
}
For Docker commands from inside the container, mount the host Docker socket and ensure that granting this access is appropriate for your environment.
The Devbox release version is tracked in .devbox-version.
task get-version to refresh it from the latest Devbox release.task check before publishing changes.taskfile.yaml; they consume .devbox-version.Content type
Image
Digest
sha256:783f96e4a…
Size
211.2 MB
Last updated
2 days ago
docker pull geoffh1977/jetify-devbox