# 0007 — Devcontainer with docker-outside-of-docker, services on host

- **Status:** Accepted
- **Date:** 2026-05-06

## Context and problem statement

The development environment must support a Node 22 LTS toolchain, pnpm, the Nx CLI, Playwright with Chromium, and connectivity to local Postgres and Redis. The target host is Windows 11 with WSL2 Ubuntu and Docker Desktop. The developer wants reproducible environments that mirror enterprise patterns.

How should the dev environment be structured? Specifically: should Postgres and Redis run inside the devcontainer, alongside it as sibling containers, or directly on the host?

## Decision drivers

- **Reproducibility.** A new machine or a fresh container should yield an identical toolchain.
- **File watcher reliability.** Vitest, Nx daemon, and Angular dev server all rely on inotify-based file watching, which behaves poorly across virtualisation boundaries.
- **Debugger attachment.** The Nest API debugger (`node --inspect`) must be reachable from the host VS Code without complicated port-forwarding rituals.
- **Service persistence across container rebuilds.** Postgres data should not vanish every time the devcontainer is rebuilt for a configuration change.
- **Twitch dev rig connectivity.** The Twitch dev rig runs on Windows and must reach the API. The networking chain (Windows → WSL2 → devcontainer → API) must be navigable.
- **Enterprise pattern alignment.** The setup should reflect patterns used in real-world enterprise devcontainers, not just solo-friendly shortcuts.

## Considered options

1. **Native WSL2, no devcontainer.** All tooling installed directly in the WSL2 Ubuntu environment.
2. **Devcontainer with all services inside it (compose-based devcontainer).** Postgres, Redis, and the dev workspace all in one Compose stack tied to the devcontainer lifecycle.
3. **Devcontainer with `docker-outside-of-docker`, services on host.** The devcontainer contains the dev toolchain only; Postgres and Redis run as plain Docker containers on the host, accessed via `host.docker.internal`.
4. **Devcontainer with services as sibling containers in Compose, but `docker-outside-of-docker`.** Variation of option 3 but with all containers managed via one Compose file.

## Decision outcome

**Chosen: Devcontainer with `docker-outside-of-docker` feature, Postgres and Redis as plain Docker containers on the host (separate from the devcontainer).**

Architecture:

```
Windows host
└── WSL2 Ubuntu
    └── Docker Desktop daemon
        ├── postgres container (host:5432)
        ├── redis container (host:6379)
        └── devcontainer (mounts repo, talks to host daemon via DooD)
            └── pnpm, nx, claude code, vscode-server
```

The devcontainer mounts the repo at `/workspaces/fog-expedition` and uses `host.docker.internal` to reach Postgres and Redis. The `docker-outside-of-docker` feature lets the developer run `docker compose` commands inside the container against the host's daemon.

## Consequences

### Positive

- **Toolchain reproducibility.** Node 22 LTS, pnpm, Nx, and Playwright are pinned in the Dockerfile. Any machine with VS Code and Docker Desktop can rebuild the environment identically.
- **Service persistence.** Postgres data survives devcontainer rebuilds because the database container has its own lifecycle and named volume.
- **Debugger simplicity.** The Nest API runs inside the devcontainer; VS Code's debugger attaches via the standard "Reopen in Container" + launch config flow.
- **File watching works.** The repo is bind-mounted into the devcontainer's Linux filesystem, where inotify behaves correctly. (It would not, were the repo mounted from `/mnt/c/`.)
- **Enterprise pattern.** This DooD setup is widely used in real-world dev environments — the developer gains transferable experience.
- **Native WSL2 fallback works.** If the devcontainer breaks, the developer can still work in native WSL2 with the same services. No lock-in.

### Negative

- **`host.docker.internal` is Docker Desktop-specific.** A pure-Linux production-style setup (without Docker Desktop) doesn't get this hostname for free; it has to be set up manually with `extra_hosts: host-gateway`. Worth knowing for future portability.
- **Two container lifecycles to manage.** Stopping the devcontainer doesn't stop Postgres and Redis; they're independent. Slight cognitive overhead.
- **Connection strings must use `host.docker.internal`, not `localhost`.** A common stumble for new contributors. Documented in `CLAUDE.md` and `TOOLCHAIN.md`.

### Neutral

- The project deliberately doesn't use a Compose-based devcontainer where all services come up together. That pattern is appropriate for projects where the database is short-lived dev-only data; this project uses the same Postgres for development across many sessions and benefits from independent service lifecycles.
- `pnpm install` runs as a `postCreateCommand`, which means devcontainer creation takes longer but immediately yields a working environment.

## Implementation notes

- Base image: `mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm` (LTS-aligned).
- `~/.gitconfig` from the host is bind-mounted into the container so git identity persists.
- Forwarded ports: 3000 (API), 4200 (overlay dev server), 5432 (Postgres), 6379 (Redis), 443 (Caddy HTTPS).
- The `node` user has passwordless sudo (a base image convenience). This is dev-only and would not appear in a production container.
