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. Multi-arch: linux/amd64 +
linux/arm64.
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 security_opt (seccomp/apparmor/label unconfined) and the
/dev/fuse device already set in docker-compose.yml. That is not --privileged
and grants no host or host-daemon access — nested containers run rootless and stay
confined to devcon.
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
privilegedand no host Docker socket mounted — the container can't reach the host or the host Docker daemon. Docker inside is rootless Podman, confined to the container. It's host-package isolation, not a security sandbox against hostile code. - Rootless Podman does require the
security_opt(seccomp/apparmor/labelunconfined) and/dev/fusedevice indocker-compose.yml— a much smaller relaxation than--privileged, with no host reach. - 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: