DevCon 4
DEFCON (Defense Readiness Condition) is the US military alert system. DEFCON 5 is peacetime. DEFCON 1 is nuclear war.
DevCon 4 is where we operate: heightened awareness, not full lockdown. Your development container is secured with a restrictive firewall, but you still have access to everything you need -- GitHub, Hex, Anthropic. Vigilant, but productive.
Devcontainer setup for Elixir projects. A Mix archive installer that generates
a complete .devcontainer/ configuration with a restrictive firewall and
development tools. Optional packages (like Claude Code) can be added with
--add.
What it generates
Three files are created in your project's .devcontainer/ directory:
| File | Purpose |
|---|---|
devcontainer.json | Container settings, volume mounts, VS Code extensions, and lifecycle commands |
Dockerfile | Elixir + Node.js image with zsh, git, and firewall tools |
init-firewall.sh | Restrictive outbound firewall allowing only essential domains |
Dockerfile details
The generated Dockerfile builds an image with:
- Elixir & Erlang -- from the official
hexpm/elixirimage (version configurable) - Node.js 20 -- for tooling and optional packages
- Shell -- zsh with git and fzf plugins
- Dev tools -- git, vim, nano, jq, gh (GitHub CLI), fzf, unzip
- Firewall tools -- iptables, ipset, iproute2, dnsutils, aggregate
- Non-root user --
developerwith passwordless sudo - Hex & Rebar -- pre-installed for Elixir development
Firewall details
The init-firewall.sh script applies a default-deny outbound policy. The base
install allows only these domains:
| Domain | Reason |
|---|---|
api.github.com, GitHub IP ranges | Git operations, GitHub CLI |
repo.hex.pm, builds.hex.pm, hex.pm | Elixir package management |
Optional packages add their own domains (e.g., --add claude adds
api.anthropic.com, registry.npmjs.org, and others).
DNS (port 53), SSH (port 22), and localhost are always allowed.
Installation
mix archive.install hex devcon4Usage
Run in any Elixir project:
mix devcon4.installOptions
All options have sensible defaults. Override them as needed:
| Option | Default | Description |
|---|---|---|
--elixir-version | detected | Elixir version for the base Docker image (from running Elixir) |
--erlang-version | detected | Erlang/OTP version for the base Docker image (from running OTP) |
--ubuntu-version | noble | Ubuntu release codename |
--ubuntu-date-tag | 20260217 |
Ubuntu image date tag from hexpm/elixir |
--timezone | America/Sao_Paulo |
Default timezone fallback (host $TZ takes priority) |
--add | Add an optional package (repeatable). See below. | |
--no-firewall | false |
Skip firewall setup (no init-firewall.sh, no NET_ADMIN capability) |
--force | false | Overwrite existing files without prompting |
Available packages
Optional packages are defined in priv/packages.yml. Each --add flag
includes the package's Dockerfile steps, devcontainer settings, and firewall
domains.
| Package | Description |
|---|---|
claude | Claude Code AI assistant (npm, firewall: Anthropic API + telemetry) |
codex | OpenAI Codex CLI assistant (npm, firewall: OpenAI API) |
gemini | Google Gemini CLI assistant (npm, firewall: Google AI API) |
postgres |
PostgreSQL client tools (psql, pg_dump, etc.) |
flyio | Fly.io CLI for deployment (firewall: Fly.io API + registry) |
Examples
Install with defaults (no optional packages):
mix devcon4.installInstall with Claude Code:
mix devcon4.install --add claudeInstall with custom versions and Claude Code:
mix devcon4.install \
--add claude \
--elixir-version 1.17.0 \
--erlang-version 26.2.1Install without the firewall (unrestricted network access):
mix devcon4.install --add claude --no-firewallPrerequisites
DevCon 4 generates configuration files for the Dev Containers open specification. To build and run the container you need:
- Docker (or a compatible container runtime like Podman)
- One of the following to consume the generated
.devcontainer/files:- The devcontainer CLI (Node.js) -- a standalone command-line tool
- VS Code with the Dev Containers extension
- Any other tool that supports the Dev Containers spec (JetBrains, GitHub Codespaces, etc.)
Installing the devcontainer CLI
The devcontainer CLI is a Node.js
package that reads devcontainer.json and manages the container lifecycle
(build, start, exec). Install it globally:
npm install -g @devcontainers/cliVerify the installation:
devcontainer --versionNote: Node.js (>= 18) is required on the host machine to run the devcontainer CLI. Node.js is also installed inside the container for Claude Code.
Starting the container
Via terminal (devcontainer CLI)
# Build and start the container
devcontainer up --workspace-folder .
# Execute commands inside the container
devcontainer exec --workspace-folder . mix test
# Enter an interactive shell
docker exec -it <container_id> zsh
# Rebuild after changing devcontainer files
devcontainer up --workspace-folder . --rebuild-if-existsVia VS Code
- Install the Dev Containers extension
- Open the project folder
-
Press
Ctrl+Shift+Pand select Dev Containers: Reopen in Container - The ElixirLS extension is automatically installed
Using Claude Code inside the container
When installed with --add claude, Claude Code is available globally:
claude # Start Claude Code
claude --dangerously-skip-permissions # Unattended mode (use with caution)
The Claude Code configuration is persisted across container rebuilds via a
Docker volume mounted at /home/developer/.claude.
Customization
After installation, the generated files are yours to modify. Common customizations:
Adding firewall domains
Edit .devcontainer/init-firewall.sh and add domains to the for domain in
loop:
for domain in \
"registry.npmjs.org" \
"api.anthropic.com" \
...
"your-domain.example.com"; do # <-- add hereAdding system packages
Edit .devcontainer/Dockerfile and add packages to the apt-get install line:
RUN apt-get update && apt-get install -y --no-install-recommends \
...
postgresql-client \ # <-- add here
&& apt-get clean && rm -rf /var/lib/apt/lists/*Adding VS Code extensions
Edit .devcontainer/devcontainer.json:
"customizations": {
"vscode": {
"extensions": [
"JakeBecker.elixir-ls",
"your.extension-id"
]
}
}Changing container settings
Edit .devcontainer/devcontainer.json. Common changes:
postCreateCommand-- add project-specific setup stepsforwardPorts-- expose ports to the host (e.g.,[4000]for Phoenix)containerEnv-- add environment variables
Security considerations
The firewall restricts outbound network access to a whitelist of domains. This provides meaningful isolation but is not a complete sandbox:
- A malicious project could still access any whitelisted domain
-
When using
--add claude, Claude Code credentials are accessible inside the container -
The
developeruser has passwordless sudo
Only use devcontainers with repositories you trust.
When using --add claude, the --dangerously-skip-permissions flag for Claude
Code is designed for use within this restricted environment. It allows Claude
Code to operate without interactive permission prompts, which is useful for
automated workflows. The firewall limits the blast radius of any unintended
actions.
Troubleshooting
Docker uses stale layers after changing devcontainer files
Docker caches build layers aggressively. If you regenerate files (e.g., with
--force) but Docker still uses old layers, rebuild without cache:
devcontainer up --workspace-folder . --build-no-cache --remove-existing-container
Changes to DevCon 4 source not reflected after mix devcon4.install
If you're developing DevCon 4 itself, remember that template files and
priv/packages.yml are compiled into the archive. After editing them, you must
force-recompile and reinstall:
mix compile --force
mix archive.build
mix archive.install devcon4-0.2.0.ez --force
Then re-run mix devcon4.install --force in the target project.
Finding available image tags
The base image tag follows the pattern
hexpm/elixir:{elixir}-erlang-{erlang}-ubuntu-{ubuntu}-{date}. To find
available tags:
# Search for tags matching your desired versions
curl -s "https://hub.docker.com/v2/repositories/hexpm/elixir/tags?page_size=100&name=1.18" \
| jq -r '.results[].name' | grep noble | sort -VLicense
Apache-2.0