A secure, parameterized development container with egress firewall control.
- Egress Firewall: IP-based allowlisting using iptables/ipset
- Persistent Caches: Named volumes for shell history, pip, uv, pnpm caches
- Pre-configured Tools: zsh, git-delta, fzf, GitHub CLI, fnm, pnpm
- Optional AI CLIs: Claude, Amp, Factory (comment out in Dockerfile if not needed)
All build-time variables are configured in .devcontainer/devcontainer.json:
| Variable | Location | Default | Description |
|---|---|---|---|
TZ |
build.args |
UTC |
Container timezone |
USER_UID |
build.args |
1000 |
Container user UID (match host for permissions) |
USER_GID |
build.args |
1000 |
Container user GID |
USE_BUN |
build.args |
false |
Also install Bun alongside Node.js |
NODE_VERSION |
build.args |
22 |
Node.js major version (installed via fnm) |
PYTHON_VERSION |
features.python |
3.13 |
Python version |
To customize: edit devcontainer.json → rebuild. No env vars or sourcing needed.
Set these in .devcontainer/.env (copy from .devcontainer/.env.example):
| Variable | Default | Description |
|---|---|---|
ENABLE_FIREWALL |
false |
Set to true to enable egress restrictions |
FIREWALL_EXTRA_DOMAINS |
`` | Comma-separated list of additional domains to allow |
The firewall allows traffic to:
- GitHub (API, web, git)
- npm registry
- PyPI (pypi.org, files.pythonhosted.org)
- VS Code Marketplace
- Anthropic API, Statsig
- Amp, Factory APIs
To add custom domains:
export FIREWALL_EXTRA_DOMAINS="api.example.com,cdn.example.org"For debugging or when you need unrestricted access:
export ENABLE_FIREWALL=false- The container runs as non-root user
devwith passwordless sudo NET_ADMINcapability is required for the firewall - this means container processes can modify iptables- The firewall is a policy enforcement tool, not a security sandbox against malicious code
- CLI installers (
curl | bash) are unpinned - audit per your org's security policy
Edit devcontainer.json → customizations.vscode.extensions
Comment out the unwanted lines in Dockerfile (near the end):
# RUN curl -fsSL https://claude.ai/install.sh | bash
# RUN curl -fsSL https://ampcode.com/install.sh | bash
# RUN curl -fsSL https://app.factory.ai/cli | shThe template uses debian:bookworm-slim. To change:
- Update
FROMinDockerfile - Adjust package manager commands if not Debian-based
- Check which domain is blocked:
curl -v https://blocked-domain.com - Add it:
export FIREWALL_EXTRA_DOMAINS="blocked-domain.com" - Rebuild container or re-run:
sudo /usr/local/bin/init-firewall.sh
If GitHub API or DNS is unreachable during startup, the firewall script will warn but continue. Check logs for warnings.
Ensure USER_UID and USER_GID match your host user:
export USER_UID=$(id -u)
export USER_GID=$(id -g)This is a development template. For production deployment, you'll need to generate deployment-specific files.
Most platforms need a requirements.txt:
uv export --no-dev > requirements.txtThe dev container Dockerfile includes dev tools, AI CLIs, firewall scripts, etc. For production, generate a minimal Dockerfile:
# Python only
./scripts/generate-prod-dockerfile.sh
# Python + Node.js
./scripts/generate-prod-dockerfile.sh --with-node
# Custom output path
./scripts/generate-prod-dockerfile.sh --output deploy/DockerfileThen edit the generated file to set your CMD entrypoint.
| Platform | What to add | Notes |
|---|---|---|
| Railway | Procfile or use Dockerfile |
web: python -m src.main |
| Render | Uses Dockerfile or requirements.txt |
Auto-detects Python |
| Fly.io | fly.toml + Dockerfile |
fly launch generates config |
| Heroku | Procfile + requirements.txt |
web: gunicorn src.main:app |
| PythonAnywhere | WSGI config in dashboard | Upload requirements.txt |
| Vercel | /api folder with handlers |
Serverless functions only |
| Cloudflare Pages | ❌ Not supported | Static/JS only |
# For web apps (FastAPI/Flask)
web: uvicorn src.main:app --host 0.0.0.0 --port ${PORT:-8000}
# For workers/scripts
worker: python -m src.workerservices:
app:
build:
context: .
dockerfile: Dockerfile.prod
ports:
- "8000:8000"
environment:
- DATABASE_URL=${DATABASE_URL}