Skip to content

Releases: ChrisSc/claude-devcontainer

v0.2.3 — gateway host port remapped to 5001 (macOS AirPlay fix)

Choose a tag to compare

@ChrisSc ChrisSc released this 15 Jun 18:17
a631ecd

Fixed

  • Gateway unreachable at localhost:5000 on macOS — the v0.2.2 publish used host
    port 5000, which macOS reserves for the AirPlay Receiver (Control Center binds
    *:5000, including IPv6 ::1). Because localhost resolves to ::1 first 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's listenPort) → browse
    https://localhost:5001. Needs a container recreate to apply.

v0.2.2 — IBKR gateway port (5000) published to host loopback

Choose a tag to compare

@ChrisSc ChrisSc released this 15 Jun 17:41
8fe3030

Added

  • Host access to the IBKR Client Portal Gateway — publish the gateway's web UI
    (clientportal.gw binds :5000 inside 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 inside CONTAINER_CIDR, so
    init-firewall.sh's INPUT allow accepts it in every mode. Applying it needs a
    container recreate (docker compose up -d / make rebuild), and the gateway's own
    root/conf.yaml ips.allow must include 172.* or it rejects the login at the app
    layer.

v0.2.1 — OpenJDK 11 (Temurin) baked in + repo rename

Choose a tag to compare

@ChrisSc ChrisSc released this 15 Jun 17:26
b4acebe

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 accesses java.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 the java / JAVA_HOME wiring, and the seeded ENVIRONMENT.md lists
    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

Choose a tag to compare

@ChrisSc ChrisSc released this 14 Jun 23:46

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 a smoke job that builds the image, boots it, and
    asserts the wiring (Claude CLI, the uv 3.14 python3 shim, the toolbelt, the seed
    doc). make lint / make smoke run the same checks locally; main is 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-check asserts the expected events fired in order.
  • IPv6 egress filtering: ip6tables is configured fail-closed in strict mode
    (with net.ipv6.conf.*.disable_ipv6 sysctls 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 seeded ENVIRONMENT.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 an EXIT trap that re-clamps OUTPUT/INPUT to DROP on
    any mid-apply abort — previously an abort there could leave egress wide open.
    Concurrent firewall runs serialize on a flock.
  • 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. A SHELL [… -o pipefail …] directive makes curl | sh build layers fail closed.
  • Lifecycle Make targets are db-profile-aware. stop / down / nuke now tear
    down the opt-in db sidecar and its volumes, so make nuke actually destroys the
    database data as documented.
  • claude update at boot is time-bounded (and remains non-fatal), so a slow
    network can't hang startup.
  • Dropped the NET_RAW capability — the firewall needs only NET_ADMIN.

Fixed

  • DNS / SSH exfiltration channels closed. Strict mode no longer allows blanket
    udp/53 or tcp/22 to any host: DNS is scoped to the resolver(s) in
    /etc/resolv.conf, and git-over-SSH reaches GitHub via the allowed-domains ipset.
  • @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 Postgrescron.env now includes the PG* /
    DATABASE_URL client vars.
  • VS Code postStartCommand swallowed firewall failures (it always exited 0); a
    firewall error now propagates while update/cron stay non-fatal.
  • make db-dump writes atomically (temp file + rename); gen-env.sh re-secures .env
    to 0600; the zsh compinit fast-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.md into a lean root plus .devcontainer/CLAUDE.md (per-script
    invariants), reconciled with the hardening above.
  • README: CI badge, --force-recreate for 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

Choose a tag to compare

@ChrisSc ChrisSc released this 10 Jun 03:17

Added

  • Scheduled agents via cron: the cron daemon is installed and started at boot, and the crontab is a real file in the persistent ~/.claude volume (~/.claude/cron/crontab) re-installed into the live spool every boot by init-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.env regenerated each boot + SHELL=/bin/bash + BASH_ENV), so claude -p runs non-interactively with the persisted ~/.claude auth. Helpers: crontab-edit / crontab-reload; make cron-reload / make cron-log.

Fixed

  • /usr/local/share/npm-global/bin appeared twice in PATH (section 4 prepended it for the build, then the final ENV prepended it again). The runtime PATH is now spelled out in full to match entrypoint.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.md and the in-container seed/CLAUDE.md; CLAUDE.md documents the cron invariants (file-not-symlink source of truth, the stripped-env reconstruction via cron.env + BASH_ENV, and the pgrep-guarded daemon start).

Full changelog: v0.1.4...v0.1.5

v0.1.4 — default timezone America/New_York + fix split-brain TZ

Choose a tag to compare

@ChrisSc ChrisSc released this 03 Jun 14:47

Changed

  • Default container timezone is now America/New_York (was America/Los_Angeles). Override per-host with the TZ env var, which compose.yaml threads into both the build arg and the runtime environment.

Fixed

  • Split-brain timezone: the base image set ENV TZ (honored by glibc CLI tools like date) but never configured /etc/localtime, so anything reading the system clock files — e.g. Python's datetime — silently fell back to Etc/UTC. The Dockerfile now installs tzdata and pins /etc/localtime + /etc/timezone from $TZ at 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.md documents the invariant: TZ lives 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