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:

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 devcon4

Usage

Run in any Elixir project:

mix devcon4.install

Options

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-versionnoble Ubuntu release codename
--ubuntu-date-tag20260217 Ubuntu image date tag from hexpm/elixir
--timezoneAmerica/Sao_Paulo Default timezone fallback (host $TZ takes priority)
--add Add an optional package (repeatable). See below.
--no-firewallfalse Skip firewall setup (no init-firewall.sh, no NET_ADMIN capability)
--forcefalse 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.install

Install with Claude Code:

mix devcon4.install --add claude

Install with custom versions and Claude Code:

mix devcon4.install \
  --add claude \
  --elixir-version 1.17.0 \
  --erlang-version 26.2.1

Install without the firewall (unrestricted network access):

mix devcon4.install --add claude --no-firewall

Prerequisites

DevCon 4 generates configuration files for the Dev Containers open specification. To build and run the container you need:

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/cli

Verify the installation:

devcontainer --version

Note: 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-exists

Via VS Code

  1. Install the Dev Containers extension
  2. Open the project folder
  3. Press Ctrl+Shift+P and select Dev Containers: Reopen in Container
  4. 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 here

Adding 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:

Security considerations

The firewall restricts outbound network access to a whitelist of domains. This provides meaningful isolation but is not a complete sandbox:

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 -V

License

Apache-2.0