README.md

# 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/elixir` image (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** -- `developer` with 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

```bash
mix archive.install hex devcon4
```

## Usage

Run in any Elixir project:

```bash
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-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):

```bash
mix devcon4.install
```

Install with Claude Code:

```bash
mix devcon4.install --add claude
```

Install with custom versions and Claude Code:

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

Install without the firewall (unrestricted network access):

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

## Prerequisites

DevCon 4 generates configuration files for the
[Dev Containers](https://containers.dev/) 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](https://github.com/devcontainers/cli) is a Node.js
package that reads `devcontainer.json` and manages the container lifecycle
(build, start, exec). Install it globally:

```bash
npm install -g @devcontainers/cli
```

Verify the installation:

```bash
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)

```bash
# 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](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-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:

```bash
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:

```bash
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:

```dockerfile
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`:

```json
"customizations": {
  "vscode": {
    "extensions": [
      "JakeBecker.elixir-ls",
      "your.extension-id"
    ]
  }
}
```

### Changing container settings

Edit `.devcontainer/devcontainer.json`. Common changes:

- `postCreateCommand` -- add project-specific setup steps
- `forwardPorts` -- 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 `developer` user 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:

```bash
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:

```bash
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:

```bash
# 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