···11+# MM-66 Docker Image Implementation Plan — Phase 1
22+33+**Goal:** Create `nix/docker.nix` and extend `flake.nix` so `docker-image` is exposed as a flake package on Linux targets only.
44+55+**Architecture:** A new file `nix/docker.nix` holds the `buildLayeredImage` derivation as a standalone Nix function `{ pkgs, relay }:`. `flake.nix` merges this into the existing `forEachSystem` lambda return value using `pkgs.lib.optionalAttrs pkgs.stdenv.isLinux { ... }`, which evaluates to `{}` on Darwin and `{ docker-image = ...; }` on Linux — making the conditional a zero-cost no-op on macOS.
66+77+**Tech Stack:** Nix flakes, nixpkgs `dockerTools.buildLayeredImage`, crane (relay binary already built by MM-65)
88+99+**Scope:** Phase 1 of 2 from the original design. Phase 2 covers build verification and CLAUDE.md update.
1010+1111+**Codebase verified:** 2026-03-08
1212+1313+---
1414+1515+## Acceptance Criteria Coverage
1616+1717+This phase implements:
1818+1919+### MM-66.AC1: docker-image outputs exist in the flake
2020+- **MM-66.AC1.1 Success:** `nix flake show --accept-flake-config` (on Linux) lists `packages.x86_64-linux.docker-image`
2121+- **MM-66.AC1.2 Success:** `nix flake show --accept-flake-config` (on Linux) lists `packages.aarch64-linux.docker-image`
2222+- **MM-66.AC1.3 Negative:** `packages.aarch64-darwin.docker-image` and `packages.x86_64-darwin.docker-image` are not present in `nix flake show` output
2323+2424+### MM-66.AC3: Image contents
2525+- **MM-66.AC3.4 Success:** `nix/docker.nix` exists and is tracked by git (`git ls-files nix/docker.nix` returns it)
2626+2727+> **Note on AC1.1 / AC1.2:** These require a Linux system to verify. On macOS, `nix flake show` will not list `docker-image` (AC1.3 is verifiable now; AC1.1 and AC1.2 are verified in Phase 2 on a Linux system or CI).
2828+2929+---
3030+3131+<!-- START_SUBCOMPONENT_A (tasks 1-3) -->
3232+3333+<!-- START_TASK_1 -->
3434+### Task 1: Create `nix/docker.nix`
3535+3636+**Files:**
3737+- Create: `nix/docker.nix`
3838+3939+**Step 1: Create the `nix/` directory and write the derivation**
4040+4141+```bash
4242+mkdir nix
4343+```
4444+4545+Then create `nix/docker.nix` with exactly this content:
4646+4747+```nix
4848+{ pkgs, relay }:
4949+pkgs.dockerTools.buildLayeredImage {
5050+ name = "relay";
5151+ tag = "latest";
5252+ contents = [ relay pkgs.sqlite.out pkgs.cacert pkgs.tzdata ];
5353+ config = {
5454+ Entrypoint = [ "${relay}/bin/relay" ];
5555+ Env = [
5656+ "SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"
5757+ "TZDIR=${pkgs.tzdata}/share/zoneinfo"
5858+ ];
5959+ };
6060+}
6161+```
6262+6363+**Explanation of each field:**
6464+- `name = "relay"`: The Docker image name that appears in `docker images`.
6565+- `tag = "latest"`: Placeholder tag; can be wired to `self.shortRev` later.
6666+- `contents`: List of derivations whose closure becomes the image filesystem. `pkgs.sqlite.out` is the runtime-library output of sqlite (carries `libsqlite3.so`); `.dev` (headers) is omitted.
6767+- `Entrypoint`: Uses Nix string interpolation — `${relay}` expands to the relay's `/nix/store/...` path at evaluation time, so the entrypoint is always tied to the exact derivation.
6868+- `SSL_CERT_FILE` / `TZDIR`: Point into the Nix store paths of `cacert` and `tzdata` so TLS and timezone lookups work inside the container.
6969+7070+**Step 2: No operational verification yet** — verification happens after flake.nix is updated in Task 2.
7171+7272+<!-- END_TASK_1 -->
7373+7474+<!-- START_TASK_2 -->
7575+### Task 2: Extend `flake.nix` to expose `docker-image` on Linux
7676+7777+**Files:**
7878+- Modify: `flake.nix:48-51`
7979+8080+**Current content at lines 48–51:**
8181+8282+```nix
8383+ in {
8484+ inherit relay;
8585+ default = relay;
8686+ }
8787+```
8888+8989+**Replace with:**
9090+9191+```nix
9292+ in {
9393+ inherit relay;
9494+ default = relay;
9595+ } // pkgs.lib.optionalAttrs pkgs.stdenv.isLinux {
9696+ docker-image = import ./nix/docker.nix { inherit pkgs relay; };
9797+ }
9898+```
9999+100100+**Why `pkgs.lib.optionalAttrs`:** Returns the attribute set when the condition is true, and `{}` otherwise. On Darwin, the merge is `{ inherit relay; default = relay; } // {} = { inherit relay; default = relay; }`. On Linux, the `docker-image` attribute is added. This keeps Darwin package outputs unchanged while adding the Linux-only output.
101101+102102+**Why `import ./nix/docker.nix { inherit pkgs relay; }`:** Calls the function in `nix/docker.nix` with the current `pkgs` (for the target system) and the crane-built `relay` derivation. This is the standard nixpkgs package-expression calling convention.
103103+104104+**Step 2: Verify the flake evaluates (catches Nix syntax errors)**
105105+106106+```bash
107107+nix flake show --accept-flake-config
108108+```
109109+110110+Expected on macOS (aarch64-darwin): Output shows `packages.aarch64-darwin` with `relay` and `default`, but **no** `docker-image`. This is correct — `pkgs.stdenv.isLinux` is `false` on Darwin.
111111+112112+Example output (macOS):
113113+```
114114+git+file:///path/to/ezpds
115115+├───devShells
116116+│ ├───aarch64-darwin
117117+│ │ └───default: development environment 'devenv-shell'
118118+│ ...
119119+└───packages
120120+ ├───aarch64-darwin
121121+ │ ├───default: package 'relay-0.1.0'
122122+ │ └───relay: package 'relay-0.1.0'
123123+ ├───aarch64-linux
124124+ │ ├───default: package 'relay-0.1.0'
125125+ │ ├───docker-image: package 'docker-image.tar.gz'
126126+ │ └───relay: package 'relay-0.1.0'
127127+ ...
128128+```
129129+130130+If the command errors with a Nix eval error, fix it before proceeding to Task 3.
131131+132132+<!-- END_TASK_2 -->
133133+134134+<!-- START_TASK_3 -->
135135+### Task 3: Track files in git and commit
136136+137137+**Files:**
138138+- `nix/docker.nix` (new)
139139+- `flake.nix` (modified)
140140+141141+**Step 1: Stage both files**
142142+143143+```bash
144144+git add nix/docker.nix flake.nix
145145+```
146146+147147+**Step 2: Verify AC3.4 — `nix/docker.nix` is tracked by git**
148148+149149+```bash
150150+git ls-files nix/docker.nix
151151+```
152152+153153+Expected output:
154154+```
155155+nix/docker.nix
156156+```
157157+158158+If empty, the file is not staged/tracked. Re-run `git add nix/docker.nix`.
159159+160160+**Step 3: Verify AC1.3 (negative) on macOS — `docker-image` is NOT present for Darwin**
161161+162162+```bash
163163+nix flake show --accept-flake-config 2>/dev/null | grep docker-image
164164+```
165165+166166+Expected: Lines for `aarch64-linux` and `x86_64-linux` only. No `aarch64-darwin` or `x86_64-darwin` lines. If `docker-image` appears under a Darwin system, the `optionalAttrs` condition is wrong — re-check Task 2.
167167+168168+**Step 4: Commit**
169169+170170+```bash
171171+git commit -m "feat(MM-66): add nix/docker.nix and expose docker-image on Linux"
172172+```
173173+174174+<!-- END_TASK_3 -->
175175+176176+<!-- END_SUBCOMPONENT_A -->
···11+# MM-66 Docker Image Implementation Plan — Phase 2
22+33+**Goal:** Update CLAUDE.md with the `docker-image` Linux-only caveat, then verify the image builds, loads, runs, and meets size constraints on a Linux system.
44+55+**Architecture:** No new Nix code. This phase has one code change (CLAUDE.md), and the remaining work is operational verification that must be executed on an x86_64-linux or aarch64-linux system (or via CI). All acceptance criteria in this phase require a Linux Docker daemon.
66+77+**Tech Stack:** Nix CLI, Docker CLI (Linux only)
88+99+**Scope:** Phase 2 of 2. Phase 1 created `nix/docker.nix` and extended `flake.nix`.
1010+1111+**Codebase verified:** 2026-03-08
1212+1313+---
1414+1515+## Acceptance Criteria Coverage
1616+1717+This phase verifies:
1818+1919+### MM-66.AC2: Image builds and loads
2020+- **MM-66.AC2.1 Success:** `nix build .#docker-image --accept-flake-config` completes without error on x86_64-linux
2121+- **MM-66.AC2.2 Success:** `nix build .#packages.aarch64-linux.docker-image --accept-flake-config` completes without error on an aarch64-linux or x86_64-linux system
2222+- **MM-66.AC2.3 Success:** `docker load < result` completes without error
2323+- **MM-66.AC2.4 Success:** `docker images` shows `relay:latest` after loading
2424+2525+### MM-66.AC3: Image contents
2626+- **MM-66.AC3.1 Success:** `docker run --rm relay:latest` exits without a "no such file" or dynamic linker error (relay binary and libsqlite3.so are present)
2727+- **MM-66.AC3.2 Success:** `docker inspect relay:latest` shows `SSL_CERT_FILE` env var pointing to a cacert store path
2828+- **MM-66.AC3.3 Success:** `docker inspect relay:latest` shows `TZDIR` env var pointing to a tzdata store path
2929+3030+### MM-66.AC4: Image size
3131+- **MM-66.AC4.1 Success:** `docker images relay` shows image size under 50 MB
3232+3333+### MM-66.AC5: Scope boundaries
3434+- **MM-66.AC5.1 Negative:** `docker run relay:latest` does not require a running HTTP server to start (relay is a stub; no HTTP health check in this ticket)
3535+3636+---
3737+3838+> **All verification in this phase requires a Linux system with Docker installed.**
3939+> On macOS, `docker-image` is not exposed (by design). Use a Linux CI runner or a remote Linux builder.
4040+4141+---
4242+4343+<!-- START_SUBCOMPONENT_A (tasks 1-2) -->
4444+4545+<!-- START_TASK_1 -->
4646+### Task 1: Update CLAUDE.md with `docker-image` Linux-only note
4747+4848+**Files:**
4949+- Modify: `CLAUDE.md:13` (after the `nix build .#relay` line)
5050+5151+**Current content at line 13:**
5252+5353+```
5454+- `nix build .#relay --accept-flake-config` - Build relay binary (output at ./result/bin/relay)
5555+```
5656+5757+**Add the following line immediately after line 13:**
5858+5959+```
6060+- `nix build .#docker-image --accept-flake-config` - Build Docker image tarball (Linux only; `docker-image` is not exposed on macOS — use a remote Linux builder or CI)
6161+```
6262+6363+The full Commands section should read:
6464+6565+```markdown
6666+## Commands
6767+- `nix develop --impure --accept-flake-config` - Enter dev shell (flags required; --impure for devenv CWD detection, --accept-flake-config activates the Cachix binary cache in nixConfig — without it, a cold build takes 20+ minutes)
6868+- `nix build .#relay --accept-flake-config` - Build relay binary (output at ./result/bin/relay)
6969+- `nix build .#docker-image --accept-flake-config` - Build Docker image tarball (Linux only; `docker-image` is not exposed on macOS — use a remote Linux builder or CI)
7070+- `cargo build` - Build all crates
7171+- `cargo test` - Run all tests
7272+- `cargo clippy` - Lint
7373+- `cargo fmt --check` - Check formatting
7474+```
7575+7676+**Verification:**
7777+7878+```bash
7979+grep "docker-image" CLAUDE.md
8080+```
8181+8282+Expected: One line mentioning `nix build .#docker-image` and noting it is Linux-only.
8383+8484+**Commit:**
8585+8686+```bash
8787+git add CLAUDE.md
8888+git commit -m "docs(MM-66): note docker-image is Linux-only in CLAUDE.md"
8989+```
9090+9191+<!-- END_TASK_1 -->
9292+9393+<!-- START_TASK_2 -->
9494+### Task 2: Verify image on Linux (human verification checklist)
9595+9696+> **Run these steps on an x86_64-linux or aarch64-linux system with Docker installed.**
9797+> This task documents acceptance-criteria verification — it is not automated.
9898+9999+---
100100+101101+**Step 1: Build x86_64-linux image (verifies MM-66.AC2.1)**
102102+103103+```bash
104104+nix build .#docker-image --accept-flake-config
105105+```
106106+107107+Expected: Exits 0. A `result` symlink appears pointing to a `.tar.gz` in the Nix store.
108108+109109+If it fails with an eval error, ensure Phase 1 commit is present and re-check `nix/docker.nix` and `flake.nix`.
110110+111111+---
112112+113113+**Step 2: Build aarch64-linux image (verifies MM-66.AC2.2)**
114114+115115+On an x86_64-linux host (cross-compilation):
116116+117117+```bash
118118+nix build .#packages.aarch64-linux.docker-image --accept-flake-config
119119+```
120120+121121+Expected: Exits 0. A `result` symlink appears for the aarch64 image.
122122+123123+> Cross-compilation for aarch64 requires binfmt\_misc QEMU support or a configured remote builder. If unavailable, skip this step and mark it verified via CI.
124124+125125+---
126126+127127+**Step 3: Load image into Docker (verifies MM-66.AC2.3 and MM-66.AC2.4)**
128128+129129+First rebuild the x86_64 image if `result` is from the aarch64 build:
130130+131131+```bash
132132+nix build .#docker-image --accept-flake-config
133133+```
134134+135135+Then load:
136136+137137+```bash
138138+docker load < result
139139+```
140140+141141+Expected output contains:
142142+```
143143+Loaded image: relay:latest
144144+```
145145+146146+Verify the image appears:
147147+148148+```bash
149149+docker images relay
150150+```
151151+152152+Expected: At least one row showing `relay` / `latest`.
153153+154154+---
155155+156156+**Step 4: Run the relay stub (verifies MM-66.AC3.1 and MM-66.AC5.1)**
157157+158158+```bash
159159+docker run --rm relay:latest
160160+```
161161+162162+Expected: The container exits. There must be **no** `no such file or directory` or `error while loading shared libraries: libsqlite3.so` error.
163163+164164+The relay is currently a stub and may exit with a non-zero code — that is acceptable. The absence of linker errors confirms `relay` binary and `libsqlite3.so` are present in the image closure.
165165+166166+---
167167+168168+**Step 5: Inspect environment variables (verifies MM-66.AC3.2 and MM-66.AC3.3)**
169169+170170+```bash
171171+docker inspect relay:latest | grep -E 'SSL_CERT_FILE|TZDIR'
172172+```
173173+174174+Expected output (store hash will differ):
175175+176176+```
177177+"SSL_CERT_FILE=/nix/store/...-nss-ca-cert-.../etc/ssl/certs/ca-bundle.crt",
178178+"TZDIR=/nix/store/...-tzdata-.../share/zoneinfo"
179179+```
180180+181181+Both variables must be present. If either is missing, `nix/docker.nix` is missing the `Env` config — re-check Task 1 of Phase 1.
182182+183183+---
184184+185185+**Step 6: Check image size (verifies MM-66.AC4.1)**
186186+187187+```bash
188188+docker images relay --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"
189189+```
190190+191191+Expected: The `SIZE` column shows a value under 50 MB (e.g., `42.3MB`).
192192+193193+If the size exceeds 50 MB, check `contents` in `nix/docker.nix` for unnecessary packages and remove them. The expected closure is: relay binary + libsqlite3.so + CA bundle + tzdata. No shell, no libc extras.
194194+195195+---
196196+197197+**Step 7: Verify `docker-image` is absent on Darwin (verifies MM-66.AC1.3)**
198198+199199+From your macOS machine:
200200+201201+```bash
202202+nix flake show --accept-flake-config 2>/dev/null | grep docker-image
203203+```
204204+205205+Expected: Lines for `aarch64-linux` and `x86_64-linux` only. No `aarch64-darwin` or `x86_64-darwin` lines appear.
206206+207207+---
208208+209209+**Step 8: Verify flake show on Linux lists both outputs (verifies MM-66.AC1.1 and MM-66.AC1.2)**
210210+211211+On the Linux system:
212212+213213+```bash
214214+nix flake show --accept-flake-config 2>/dev/null | grep docker-image
215215+```
216216+217217+Expected output includes:
218218+```
219219+│ ├───aarch64-linux
220220+│ │ ├───docker-image: package 'docker-image.tar.gz'
221221+│ ...
222222+│ ├───x86_64-linux
223223+│ │ ├───docker-image: package 'docker-image.tar.gz'
224224+```
225225+226226+Both `aarch64-linux` and `x86_64-linux` must show `docker-image`.
227227+228228+<!-- END_TASK_2 -->
229229+230230+<!-- END_SUBCOMPONENT_A -->
···11+# MM-66 Test Requirements
22+33+## Overview
44+55+MM-66 is a Nix/Docker packaging ticket. There is no Rust application logic under test -- the implementation consists entirely of Nix derivation files (`nix/docker.nix` and a `flake.nix` extension). Consequently, there are no Rust unit tests or integration tests in scope. All verification is operational: checking that Nix flake outputs exist, that the built Docker image loads and runs correctly, and that the image meets size and content constraints.
66+77+Verification splits into two categories:
88+99+1. **Automated (CI-able on Linux):** Checks that can be scripted and run in a Linux CI environment with Nix and Docker installed. These are shell commands with deterministic expected output.
1010+2. **Human verification (Linux required):** Checks that require a running Docker daemon on a Linux system. These cannot be run on macOS (where the current development happens) because `docker-image` is intentionally not exposed for Darwin targets. A developer must either use a Linux machine, a remote Linux builder, or a Linux CI runner.
1111+1212+In practice, every AC in this ticket requires Linux for full verification. The distinction below separates checks that are purely Nix evaluation (can run without Docker) from checks that require both Nix and a Docker daemon.
1313+1414+## Automated Tests
1515+1616+These checks can be scripted in CI. They verify Nix flake structure and file tracking -- no Docker daemon required.
1717+1818+| AC ID | Test Type | Verification Command | What It Verifies |
1919+|---|---|---|---|
2020+| AC1.3 | Nix flake evaluation (macOS or Linux) | `nix flake show --accept-flake-config 2>/dev/null \| grep docker-image` | `docker-image` appears only under `aarch64-linux` and `x86_64-linux` -- never under `aarch64-darwin` or `x86_64-darwin`. Verifiable on any platform because `nix flake show` evaluates all systems. |
2121+| AC3.4 | Git file tracking | `git ls-files nix/docker.nix` | `nix/docker.nix` exists and is tracked by git (output is `nix/docker.nix`). |
2222+2323+**Notes:**
2424+- AC1.3 is the only acceptance criterion fully verifiable on macOS. The `nix flake show` output lists all systems regardless of the host platform, so a grep for `docker-image` under Darwin systems can confirm absence.
2525+- AC3.4 is a simple git check with no platform dependency.
2626+2727+## Human Verification (Linux Required)
2828+2929+All remaining ACs require a Linux system with both Nix and Docker installed. The commands below are taken directly from Phase 2, Task 2 of the implementation plan.
3030+3131+### AC1: docker-image outputs exist in the flake
3232+3333+| AC ID | Verification Command | Expected Result | Justification for Human Verification |
3434+|---|---|---|---|
3535+| AC1.1 | `nix flake show --accept-flake-config 2>/dev/null \| grep docker-image` | Output includes `packages.x86_64-linux.docker-image` (or the tree-formatted equivalent showing `docker-image: package 'docker-image.tar.gz'` under `x86_64-linux`) | While `nix flake show` works on any platform, confirming the output is correct on an actual Linux system validates that the conditional evaluation produces the expected attribute. Can be automated in Linux CI. |
3636+| AC1.2 | `nix flake show --accept-flake-config 2>/dev/null \| grep docker-image` | Output includes `packages.aarch64-linux.docker-image` (or the tree-formatted equivalent showing `docker-image: package 'docker-image.tar.gz'` under `aarch64-linux`) | Same as AC1.1. Both architectures must appear. |
3737+3838+### AC2: Image builds and loads
3939+4040+| AC ID | Verification Command | Expected Result | Justification for Human Verification |
4141+|---|---|---|---|
4242+| AC2.1 | `nix build .#docker-image --accept-flake-config` | Exits 0. A `result` symlink appears pointing to a `.tar.gz` in the Nix store. | Requires Linux -- `docker-image` is not a valid flake output on Darwin. The Nix build actually compiles the image derivation. |
4343+| AC2.2 | `nix build .#packages.aarch64-linux.docker-image --accept-flake-config` | Exits 0. A `result` symlink appears for the aarch64 image. | Requires Linux (or cross-compilation with binfmt_misc QEMU support). May need to be verified via CI if no aarch64 system is available. |
4444+| AC2.3 | `docker load < result` | Output contains `Loaded image: relay:latest`. | Requires Docker daemon on Linux. |
4545+| AC2.4 | `docker images relay` | At least one row showing `relay` / `latest`. | Requires Docker daemon on Linux. |
4646+4747+### AC3: Image contents
4848+4949+| AC ID | Verification Command | Expected Result | Justification for Human Verification |
5050+|---|---|---|---|
5151+| AC3.1 | `docker run --rm relay:latest` | Container exits without `no such file or directory` or `error while loading shared libraries: libsqlite3.so` errors. Non-zero exit code is acceptable (relay is a stub). | Requires Docker daemon on Linux. Validates that the relay binary and libsqlite3.so are present in the image closure. |
5252+| AC3.2 | `docker inspect relay:latest \| grep -E 'SSL_CERT_FILE'` | Output shows `SSL_CERT_FILE=/nix/store/...-nss-ca-cert-.../etc/ssl/certs/ca-bundle.crt` (exact store hash varies). | Requires Docker daemon on Linux. Validates the cacert environment variable is set in the image config. |
5353+| AC3.3 | `docker inspect relay:latest \| grep -E 'TZDIR'` | Output shows `TZDIR=/nix/store/...-tzdata-.../share/zoneinfo` (exact store hash varies). | Requires Docker daemon on Linux. Validates the tzdata environment variable is set in the image config. |
5454+5555+### AC4: Image size
5656+5757+| AC ID | Verification Command | Expected Result | Justification for Human Verification |
5858+|---|---|---|---|
5959+| AC4.1 | `docker images relay --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"` | SIZE column shows a value under 50 MB. | Requires Docker daemon on Linux. Image must be loaded first (AC2.3). |
6060+6161+### AC5: Scope boundaries
6262+6363+| AC ID | Verification Command | Expected Result | Justification for Human Verification |
6464+|---|---|---|---|
6565+| AC5.1 | `docker run --rm relay:latest` | Container exits (same command as AC3.1). The relay does not attempt to start an HTTP server or listen on a port. No health check endpoint is tested. | Requires Docker daemon on Linux. Confirms the relay is a stub -- packaging only, no HTTP functionality in this ticket. |
6666+6767+## AC Coverage Summary
6868+6969+| AC ID | Description | Category | Phase | Verification Platform |
7070+|---|---|---|---|---|
7171+| AC1.1 | `nix flake show` lists `packages.x86_64-linux.docker-image` | Human (CI-automatable on Linux) | Phase 2, Step 8 | Linux |
7272+| AC1.2 | `nix flake show` lists `packages.aarch64-linux.docker-image` | Human (CI-automatable on Linux) | Phase 2, Step 8 | Linux |
7373+| AC1.3 | `docker-image` absent for Darwin systems | Automated | Phase 1, Task 3 / Phase 2, Step 7 | Any (macOS or Linux) |
7474+| AC2.1 | `nix build .#docker-image` succeeds on x86_64-linux | Human | Phase 2, Step 1 | x86_64-linux |
7575+| AC2.2 | `nix build .#packages.aarch64-linux.docker-image` succeeds | Human | Phase 2, Step 2 | aarch64-linux (or x86_64-linux with binfmt) |
7676+| AC2.3 | `docker load < result` succeeds | Human | Phase 2, Step 3 | Linux (Docker daemon) |
7777+| AC2.4 | `docker images` shows `relay:latest` | Human | Phase 2, Step 3 | Linux (Docker daemon) |
7878+| AC3.1 | `docker run --rm relay:latest` exits without linker errors | Human | Phase 2, Step 4 | Linux (Docker daemon) |
7979+| AC3.2 | `docker inspect` shows `SSL_CERT_FILE` env var | Human | Phase 2, Step 5 | Linux (Docker daemon) |
8080+| AC3.3 | `docker inspect` shows `TZDIR` env var | Human | Phase 2, Step 5 | Linux (Docker daemon) |
8181+| AC3.4 | `nix/docker.nix` tracked by git | Automated | Phase 1, Task 3 | Any |
8282+| AC4.1 | Image size under 50 MB | Human | Phase 2, Step 6 | Linux (Docker daemon) |
8383+| AC5.1 | Relay is a stub; no HTTP server required | Human | Phase 2, Step 4 | Linux (Docker daemon) |