A personal, self-contained dev environment container, for my own use.
An encapsulated place to work, so anything the tooling does stays inside the container instead of touching the host.
- Core development: git, GitHub CLI (
gh), Python 3 withpip/venv, Node/npm, and a native build toolchain for Python and npm packages. - AI coding: Claude Code (
claude) and Codex CLI (codex). - Editors and sessions: code-server, Vim (
vi/vim), and GNU Screen (screen). - Files and search:
less,tree,file,zip/unzip,rg,fd, andjq. (fdis provided as the conventional alias for Ubuntu'sfdfind.) - Network diagnostics:
curl,wget,ping,nslookup,ip,nc, andtraceroute. - Process diagnostics:
ps/top,lsof,strace, andhtop. - Shell productivity: Bash completion,
fzf,direnv, andshellcheck. - Remote and containers: key-only SSH plus rootless Podman through
docker/docker compose, without a host daemon.
Built on ghcr.io/linuxserver/code-server for a maintained code-server with s6
init and PUID/PGID handling, so files created in the volume are owned by my
own user, not root.
Built and pushed automatically to ghcr.io/dermute/devcon:latest by GitHub
Actions — on every push to main, weekly (to pick up base image, Node and
Claude Code updates), and on manual dispatch. Architecture: linux/amd64.
cp .env.example .env # set DEVCON_PASSWORD, ports, config path
docker compose up -dThen:
- code-server →
http://<host>:8443(password from.env) - ssh →
ssh -p 2222 <DEVCON_USER>@<host>(abcby default)
Key-only by default. Provide a public key one of two ways:
# via .env — installed into authorized_keys on startup
DEVCON_SSH_PUBKEY="ssh-ed25519 AAAA... you@host"
# or drop it into the volume yourself
cat ~/.ssh/id_ed25519.pub >> <config>/.ssh/authorized_keysTo use a password instead, set DEVCON_SSH_PASSWORD_AUTH=true and
DEVCON_SSH_PASSWORD=... in .env. Key auth stays enabled alongside it.
Docker inside the container is provided by rootless Podman — no daemon on the
host, no host Docker socket. The docker command, docker compose, and a
DOCKER_HOST socket all work out of the box:
docker run --rm hello-world
docker build -t myapp .
docker compose up -ddockeris a shim overpodman;podmanworks directly too.docker composeis upstream Compose v2 (the distro package, so it tracks the base image on each weekly rebuild — no pinned version), driving the Podman socket viaDOCKER_HOST. podman-compose is installed as a fallback provider.- Images and containers persist under
/config(the volume), so they survive rebuilds. DOCKER_HOSTpoints at a rootless Podman API socket, so other tools that speak the Docker API — the VS Code Docker extension, testcontainers — work too.- Set
DEVCON_DOCKER=falsein.envto turn the API socket off.
This needs the outer devcon container to run with privileged: true, plus
/dev/fuse, as configured in docker-compose.yml. The privilege is required at
the outer boundary so newuidmap can create nested user namespaces and startup
can make the mount tree recursively shared. Podman and the containers it starts
still run as the non-root login user, and no host Docker/Podman socket is mounted.
This is nevertheless a meaningful security relaxation: only run this image on a
host where devcon and its users are trusted. If nested containers are not needed,
remove privileged, security_opt, and devices from the Compose file and set
DEVCON_DOCKER=false.
gh auth login # GitHub
claude # Claude Code — follow the login flow
codex # Codex CLI — follow the login flowBoth persist under the /config volume, so they survive rebuilds.
| Env var | Default | Purpose |
|---|---|---|
DEVCON_PASSWORD |
— | code-server web password |
DEVCON_WEB_PORT |
8443 |
host port for the code-server UI |
DEVCON_SSH_PORT |
2222 |
host port for ssh |
DEVCON_CONFIG |
/.config |
host path for the persisted /config |
DEVCON_USER |
abc |
login username inside the container |
DEVCON_PUID / DEVCON_PGID |
1000/100 |
user/group id that owns files in the volume |
DEVCON_SSH_PUBKEY |
— | public key installed into authorized_keys |
DEVCON_SSH_PASSWORD_AUTH |
false |
allow ssh password login (else key-only) |
DEVCON_SSH_PASSWORD |
— | login user's ssh password (when password auth is on) |
DEVCON_DOCKER |
true |
rootless podman API socket + DOCKER_HOST |
GIT_USER_NAME |
— | git config --global user.name |
GIT_USER_EMAIL |
— | git config --global user.email |
- No host Docker/Podman socket is mounted; Docker inside uses an independent,
rootless Podman store under
/config. - The outer devcon container is privileged so nested rootless Podman can create user and mount namespaces. Treat devcon as a trusted personal environment, not as a security boundary against hostile code.
- Passwords are passed as env vars, so
DEVCON_PASSWORD/DEVCON_SSH_PASSWORDare visible indocker inspectand to any process in the container. Fine for a personal box; use composesecretsif that ever stops being true. DEVCON_USERmust be a normal lowercase login name —rootand malformed names are rejected at startup and fall back toabc.- To update:
docker compose pull && docker compose up -d.
This project was developed with AI assistance from: