Releases: ChrisSc/claude-devcontainer
Releases · ChrisSc/claude-devcontainer
Release list
v0.2.3 — gateway host port remapped to 5001 (macOS AirPlay fix)
Fixed
- Gateway unreachable at
localhost:5000on macOS — the v0.2.2 publish used host
port 5000, which macOS reserves for the AirPlay Receiver (Control Center binds
*:5000, including IPv6::1). Becauselocalhostresolves to::1first on
macOS, the browser hit AirPlay instead of the IPv4-only Docker publish and never
reached the gateway. Remap the host side to 5001 (127.0.0.1:5001:5000;
container side stays 5000 to match the gateway'slistenPort) → browse
https://localhost:5001. Needs a container recreate to apply.
v0.2.2 — IBKR gateway port (5000) published to host loopback
Added
- Host access to the IBKR Client Portal Gateway — publish the gateway's web UI
(clientportal.gwbinds:5000inside the container) to the host loopback only
(127.0.0.1:5000:5000), matching the db sidecar's never-public posture. The egress
firewall does not block this inbound path: the host connection arrives sourced from
the Docker bridge gateway (172.x.0.1), which is insideCONTAINER_CIDR, so
init-firewall.sh'sINPUTallow accepts it in every mode. Applying it needs a
container recreate (docker compose up -d/make rebuild), and the gateway's own
root/conf.yamlips.allowmust include172.*or it rejects the login at the app
layer.
v0.2.1 — OpenJDK 11 (Temurin) baked in + repo rename
Added
- Java runtime — Eclipse Temurin OpenJDK 11.0.31+11, baked into the image for
JVM workloads (e.g. the IBKR Client Portal Gateway). Pinned to 11 (not 17+) on
purpose — a bundled netty reflectively accessesjava.nio.DirectByteBuffer, fatal
on 17+. Fetched from Adoptium's GitHub releases with a per-arch SHA-256 gate, in
line with the project's pinned/integrity-verified build convention.make smoke
now asserts thejava/JAVA_HOMEwiring, and the seededENVIRONMENT.mdlists
the JVM.
Changed
- Repository renamed to claude-devcontainer; in-repo references and the README CI
badge updated to the new path.
v0.2.0 — security/quality hardening + first CI pipeline
A security/quality hardening pass driven by a full audit (summarized in
docs/findings/REMEDIATION.md), plus the project's first CI pipeline. Run
make rebuild to pick up the build-time and firewall changes on an existing
container.
Added
- Continuous integration (
.github/workflows/ci.yaml): shellcheck, hadolint,
and yamllint static gates plus asmokejob that builds the image, boots it, and
asserts the wiring (Claude CLI, the uv 3.14python3shim, the toolbelt, the seed
doc).make lint/make smokerun the same checks locally;mainis now
branch-protected on these checks, and the README carries a CI status badge. - Boot-event observability (
log-event.sh): the entrypoint, firewall, seed, and
cron phases emit a JSONL lifecycle trail under~/.claude, correlated by a per-boot
BOOT_ID.make boot-checkasserts the expected events fired in order. - IPv6 egress filtering:
ip6tablesis configured fail-closed in strict mode
(withnet.ipv6.conf.*.disable_ipv6sysctls as a backstop), so a dual-stack host no
longer bypasses the allowlist over IPv6. - Container healthcheck asserting the effective firewall mode, the Claude CLI, the
cron daemon, and the seededENVIRONMENT.md. .dockerignore(default-deny) so the generated.env(the Postgres password)
never enters the build context.
Changed
- Firewall fails closed. The window where egress is opened to fetch the GitHub/AWS
IP feeds is now guarded by anEXITtrap that re-clampsOUTPUT/INPUTtoDROPon
any mid-apply abort — previously an abort there could leave egress wide open.
Concurrent firewall runs serialize on aflock. - Reproducible, tamper-evident build. The base image is digest-pinned and every
fetched tool (yq, lazygit, AWS CLI, cargo-binstall, pnpm, the npm globals, uv) is
version-pinned and verified by SHA-256 or GPG signature before use; the two
third-party apt keys (GitHub CLI, PGDG) are fingerprint-verified. ASHELL [… -o pipefail …]directive makescurl | shbuild layers fail closed. - Lifecycle Make targets are db-profile-aware.
stop/down/nukenow tear
down the opt-indbsidecar and its volumes, somake nukeactually destroys the
database data as documented. claude updateat boot is time-bounded (and remains non-fatal), so a slow
network can't hang startup.- Dropped the
NET_RAWcapability — the firewall needs onlyNET_ADMIN.
Fixed
- DNS / SSH exfiltration channels closed. Strict mode no longer allows blanket
udp/53ortcp/22to any host: DNS is scoped to the resolver(s) in
/etc/resolv.conf, and git-over-SSH reaches GitHub via theallowed-domainsipset. @aws-ip-ranges <region>narrowing loaded zero CIDRs (a jq bug) and could break
AWS login; it now filters correctly and always retains the GLOBAL/CloudFront prefixes.- Cron jobs couldn't reach Postgres —
cron.envnow includes thePG*/
DATABASE_URLclient vars. - VS Code
postStartCommandswallowed firewall failures (it always exited 0); a
firewall error now propagates while update/cron stay non-fatal. make db-dumpwrites atomically (temp file + rename);gen-env.shre-secures.env
to0600; the zshcompinitfast-path guard (which was always-true) is fixed.
Documentation
- Security/quality audit under
docs/findings/, with a remediation summary
(docs/findings/REMEDIATION.md). - Split
CLAUDE.mdinto a lean root plus.devcontainer/CLAUDE.md(per-script
invariants), reconciled with the hardening above. - README: CI badge,
--force-recreatefor the permissive-mode switch, and a DB
quickstart precondition.
Full changelog: v0.1.5...v0.2.0
v0.1.5 — scheduled agents (cron) + PATH de-dup
Added
- Scheduled agents via cron: the
crondaemon is installed and started at boot, and the crontab is a real file in the persistent~/.claudevolume (~/.claude/cron/crontab) re-installed into the live spool every boot byinit-cron.sh. Symlinking the spool into a volume can't work — Debian's Vixie cron silently ignores symlinked / wrong-perm crontabs — so the file is the source of truth and survives rebuilds. Jobs run with a reconstructed environment (cron.envregenerated each boot +SHELL=/bin/bash+BASH_ENV), soclaude -pruns non-interactively with the persisted~/.claudeauth. Helpers:crontab-edit/crontab-reload;make cron-reload/make cron-log.
Fixed
/usr/local/share/npm-global/binappeared twice inPATH(section 4 prepended it for the build, then the finalENVprepended it again). The runtimePATHis now spelled out in full to matchentrypoint.sh, so it's single-entry and no longer leaks the duplicate into snapshots of the live env (e.g.cron.env).
Documentation
- New "Scheduled agents (cron)" sections in
README.mdand the in-containerseed/CLAUDE.md;CLAUDE.mddocuments the cron invariants (file-not-symlink source of truth, the stripped-env reconstruction viacron.env+BASH_ENV, and thepgrep-guarded daemon start).
Full changelog: v0.1.4...v0.1.5
v0.1.4 — default timezone America/New_York + fix split-brain TZ
Changed
- Default container timezone is now America/New_York (was
America/Los_Angeles). Override per-host with theTZenv var, whichcompose.yamlthreads into both the build arg and the runtime environment.
Fixed
- Split-brain timezone: the base image set
ENV TZ(honored by glibc CLI tools likedate) but never configured/etc/localtime, so anything reading the system clock files — e.g. Python'sdatetime— silently fell back toEtc/UTC. The Dockerfile now installstzdataand pins/etc/localtime+/etc/timezonefrom$TZat build time so the env var and system files agree. A zone switch requires a rebuild (the zone is baked into/etc/localtime).
Documentation
- README gains a "Timezone" section (override via
TZ, rebuild caveat).CLAUDE.mddocuments the invariant:TZlives in three places that must agree, and the clock is the host kernel's — no in-container NTP.
Full changelog: v0.1.3...v0.1.4