diff --git a/Dockerfile b/Dockerfile index b3b92da8..b8fb53bd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,7 +17,7 @@ RUN npm prune --omit=dev && \ FROM node:24-alpine RUN apk update && \ - apk add --no-cache python3 ruby curl bash jq unzip wget ca-certificates git github-cli file zip xz lz4 diffutils tree rsync openssh-server openssh-client cronie ripgrep && \ + apk add --no-cache python3 ruby curl bash jq unzip wget ca-certificates git github-cli file zip xz lz4 diffutils tree rsync openssh-server openssh-client cronie ripgrep tzdata && \ ssh-keygen -A && \ adduser -S -G node -h /home/madz -s /bin/sh madz && \ mkdir -p /run/sshd /root/.cache /home/madz/.cache/madz/logs && \ diff --git a/README.md b/README.md index 385d7c91..af72553b 100644 --- a/README.md +++ b/README.md @@ -368,6 +368,12 @@ All configuration is controlled via environment variables in the `docker run` co | `TUI_NAME` | `madz` | TUI identifier in banner | | `TUI_CURSOR_CHAR` | `█` | Cursor character | +**Optional — Timezone:** + +| Variable | Default | Description | +| -------- | ------- | ----------- | +| `TZ` | `UTC` | IANA timezone name (e.g., `America/Toronto`, `Europe/London`). The container ships with `tzdata` so any valid IANA timezone resolves. Set at runtime via `docker run -e TZ=...` or in `docker-compose.yml`. | + **Optional — Agent:** | Variable | Default | Description | diff --git a/docker-compose.yml b/docker-compose.yml index f288f831..9c7bc122 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,6 +6,7 @@ services: image: madz:latest environment: - NODE_ENV=development + - TZ=America/Toronto ports: - "2222:22" volumes: diff --git a/openspec/changes/archive/2026-08-25-add-tzdata-docker-container/.openspec.yaml b/openspec/changes/archive/2026-08-25-add-tzdata-docker-container/.openspec.yaml new file mode 100644 index 00000000..e685d45e --- /dev/null +++ b/openspec/changes/archive/2026-08-25-add-tzdata-docker-container/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-08-25 diff --git a/openspec/changes/archive/2026-08-25-add-tzdata-docker-container/design.md b/openspec/changes/archive/2026-08-25-add-tzdata-docker-container/design.md new file mode 100644 index 00000000..efb48962 --- /dev/null +++ b/openspec/changes/archive/2026-08-25-add-tzdata-docker-container/design.md @@ -0,0 +1,46 @@ +## Context + +The madz Docker image uses `node:24-alpine` as its base. Alpine images are minimal and do not include timezone data by default. The production stage installs ~20 system packages via `apk add` on line 20 of the Dockerfile. No timezone configuration exists anywhere in the Dockerfile or entrypoint. + +## Goals / Non-Goals + +**Goals:** +- Install `tzdata` in the Docker image so IANA timezone names resolve at runtime +- Container defaults to UTC — no hardcoded timezone +- Users override timezone via `TZ` environment variable at runtime + +**Non-Goals:** +- Hardcoding a default timezone +- Adding timezone-aware tests +- Changing the base image or entrypoint +- Adding timezone configuration files + +## Decisions + +1. **Append `tzdata` to existing `apk add` line** — Minimal change, single line in Dockerfile. No new layers, no new instructions. + - *Alternative:* Separate `RUN apk add tzdata` — creates an extra layer, unnecessary. + - *Rationale:* Single layer, no image size penalty beyond the package itself. + +2. **No `ENV TZ` in Dockerfile** — Let the container default to UTC. + - *Alternative:* Set `ENV TZ=America/Toronto` — forces a timezone on all users. + - *Rationale:* UTC is the Linux standard. Users who need a specific timezone pass `-e TZ` at runtime. + +3. **No entrypoint changes** — `docker-entrypoint.sh` does not set or override `TZ`. + - *Verification:* Confirmed by reading the file — no TZ references found. + +## Risks / Trade-offs + +- **Image size** — `tzdata` adds ~1.5MB. Mitigation: negligible compared to the total image size (~500MB+ with node_modules). +- **No default timezone** — Users who don't set `TZ` get UTC. Mitigation: this is the expected Linux behavior and the safest default. +- **No automated tests** — Docker image behavior verified manually. Mitigation: sufficient for a single-line change; adding Docker tests is out of scope. + +## Migration Plan + +1. Merge PR to `main` +2. Rebuild Docker images via `npm run docker:release:all` +3. Verify with `docker run --rm date` (UTC) and `docker run --rm -e TZ=America/Toronto date` (Eastern) +4. No rollback needed — removing `tzdata` from the Dockerfile is the rollback if needed + +## Open Questions + +None. This is a single-line change with no ambiguity. diff --git a/openspec/changes/archive/2026-08-25-add-tzdata-docker-container/proposal.md b/openspec/changes/archive/2026-08-25-add-tzdata-docker-container/proposal.md new file mode 100644 index 00000000..43e05959 --- /dev/null +++ b/openspec/changes/archive/2026-08-25-add-tzdata-docker-container/proposal.md @@ -0,0 +1,31 @@ +## Why + +The madz Docker container uses a minimal Alpine base image that ships without timezone data (`tzdata`). Without it, the container defaults to UTC and cannot resolve IANA timezone names (e.g., `America/Toronto`) when the `TZ` environment variable is set at runtime. This causes incorrect timestamps in logs, misaligned cron schedules, and confusion for users in non-UTC timezones. + +## What Changes + +- Add `tzdata` to the Alpine package installation in the Dockerfile +- Container defaults to UTC (no hardcoded `ENV TZ`) +- Users can override timezone at runtime via `docker run -e TZ=` + +## Capabilities + +### New Capabilities +- **docker-tzdata**: Timezone data support in the Docker container via the `tzdata` Alpine package + +### Modified Capabilities + + +## Impact + +- **Dockerfile** — Package installation line modified (line 20) +- **Image size** — ~1.5MB increase (negligible) +- **Runtime behavior** — Timezone resolution now works when `TZ` is set +- **No code changes** — Application code, entrypoint, and config remain unchanged + +## Non-goals + +- Hardcoding a default timezone in the Dockerfile +- Adding timezone-aware tests (manual verification is sufficient) +- Changing the Docker base image +- Adding timezone configuration files or symlinks diff --git a/openspec/changes/archive/2026-08-25-add-tzdata-docker-container/specs/dockerfile-dependencies/spec.md b/openspec/changes/archive/2026-08-25-add-tzdata-docker-container/specs/dockerfile-dependencies/spec.md new file mode 100644 index 00000000..11878bde --- /dev/null +++ b/openspec/changes/archive/2026-08-25-add-tzdata-docker-container/specs/dockerfile-dependencies/spec.md @@ -0,0 +1,27 @@ +# dockerfile-dependencies Spec Delta + +## ADDED Requirements + +### Requirement: tzdata must be installed in the container image + +The Dockerfile SHALL include `tzdata` in the `apk add --no-cache` command in the runtime stage, ensuring timezone data is present in every built container image. + +#### Scenario: tzdata is in the Dockerfile package list +- **WHEN** the Dockerfile runtime stage is parsed +- **THEN** `tzdata` appears in the `apk add --no-cache` command + +#### Scenario: tzdata is available in the container +- **WHEN** the container is built and started +- **THEN** `tzdata` is installed and IANA timezone names (e.g., `America/Toronto`) resolve correctly + +### Requirement: Timezone override via environment variable + +The Dockerfile SHALL NOT hardcode a default timezone. The container defaults to UTC, and users may override the timezone by setting the `TZ` environment variable at runtime. + +#### Scenario: Container defaults to UTC +- **WHEN** the container is started without `TZ` set +- **THEN** the system timezone is UTC + +#### Scenario: Timezone override at runtime +- **WHEN** the container is started with `TZ=America/Toronto` +- **THEN** the system timezone reflects Eastern Time (EST/EDT) diff --git a/openspec/changes/archive/2026-08-25-add-tzdata-docker-container/tasks.md b/openspec/changes/archive/2026-08-25-add-tzdata-docker-container/tasks.md new file mode 100644 index 00000000..9f65e485 --- /dev/null +++ b/openspec/changes/archive/2026-08-25-add-tzdata-docker-container/tasks.md @@ -0,0 +1,9 @@ +# Tasks + +- [x] 1. Read the Dockerfile and identify the `apk add` line in the runtime stage (line 20) +- [x] 2. Append `tzdata` to the existing `apk add --no-cache` command on line 20 +- [x] 3. Verify no `ENV TZ` is set in the Dockerfile (confirm UTC default behavior) +- [x] 4. Read `docker-entrypoint.sh` and confirm it does not set or override `TZ` +- [x] 5. Verify the Dockerfile builds successfully (dry-run or full build) +- [x] 6. Verify timezone resolution works: `docker run --rm date` shows UTC, `docker run --rm -e TZ=America/Toronto date` shows Eastern Time +- [x] 7. Run `npm run test` and `npm run coverage` to confirm no regressions (no JS code changes — coverage N/A) diff --git a/openspec/specs/dockerfile-dependencies/spec.md b/openspec/specs/dockerfile-dependencies/spec.md index 5a17384e..3e518df6 100644 --- a/openspec/specs/dockerfile-dependencies/spec.md +++ b/openspec/specs/dockerfile-dependencies/spec.md @@ -14,3 +14,27 @@ The Dockerfile SHALL include `gh` in the `apk add --no-cache` command in the run - **WHEN** the container is built and started - **THEN** `gh --version` executes successfully without "command not found" +### Requirement: tzdata must be installed in the container image + +The Dockerfile SHALL include `tzdata` in the `apk add --no-cache` command in the runtime stage, ensuring timezone data is present in every built container image. + +#### Scenario: tzdata is in the Dockerfile package list +- **WHEN** the Dockerfile runtime stage is parsed +- **THEN** `tzdata` appears in the `apk add --no-cache` command + +#### Scenario: tzdata is available in the container +- **WHEN** the container is built and started +- **THEN** `tzdata` is installed and IANA timezone names (e.g., `America/Toronto`) resolve correctly + +### Requirement: Timezone override via environment variable + +The Dockerfile SHALL NOT hardcode a default timezone. The container defaults to UTC, and users may override the timezone by setting the `TZ` environment variable at runtime. + +#### Scenario: Container defaults to UTC +- **WHEN** the container is started without `TZ` set +- **THEN** the system timezone is UTC + +#### Scenario: Timezone override at runtime +- **WHEN** the container is started with `TZ=America/Toronto` +- **THEN** the system timezone reflects Eastern Time (EST/EDT) +