candle-annotator/openspec/changes/archive/2026-02-20-code-review-fix/specs/docker-deployment/spec.md
Marko Djordjevic 925e7284e3 Archive code-review-fix change and sync specs to main
- Synced 14 capability delta specs to main specs
- Created 6 new main specs: api-authentication, error-boundary, input-validation, security-headers, shared-types
- Updated 8 existing specs with security, validation, and performance requirements
- Archived change to openspec/changes/archive/2026-02-20-code-review-fix/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 08:54:59 +01:00

6.6 KiB

ADDED Requirements

Requirement: ML service non-root user

The ML service Dockerfile SHALL create a non-root user and run the application as that user. The Dockerfile SHALL include RUN useradd -m -r appuser and USER appuser directives.

Scenario: Container runs as non-root

  • WHEN the ML service container starts
  • THEN the application process runs as user appuser (not root)

Requirement: TA-Lib downloaded over HTTPS with checksum

The ML service Dockerfile SHALL download TA-Lib source over HTTPS (not HTTP). The download SHALL be verified with a SHA256 checksum before extraction.

Scenario: HTTPS download

  • WHEN the Dockerfile downloads TA-Lib source
  • THEN the URL uses https:// protocol

Scenario: Checksum verification

  • WHEN the TA-Lib tarball is downloaded
  • THEN a sha256sum -c check runs before extraction, and the build fails if the checksum does not match

Requirement: .dockerignore file exists

The project SHALL include a .dockerignore file at the repository root that excludes .git, .env, .env*, node_modules, .next, data/, *.md, __pycache__/, mlruns/, and models/.

Scenario: Docker context excludes sensitive files

  • WHEN docker build runs
  • THEN .env, .git, and node_modules are not included in the build context

MODIFIED Requirements

Requirement: Docker Compose configuration

The project SHALL include docker-compose.yml for simplified deployment orchestration.

Scenario: Service definition

  • WHEN docker-compose.yml is parsed
  • THEN defines service named 'candle-annotator' using Dockerfile from current directory

Scenario: Port mapping

  • WHEN docker-compose up runs
  • THEN maps host port 3000 to container port 3000

Scenario: Volume mounting for ML data

  • WHEN docker-compose up runs
  • THEN mounts named volume 'ml-data' to /app/ml-data in the candle-annotator container

Scenario: Frontend depends on PostgreSQL

  • WHEN docker-compose up runs
  • THEN the candle-annotator service starts only after the postgres service is healthy (depends_on: postgres: condition: service_healthy)

Scenario: Frontend DATABASE_URL uses env var interpolation

  • WHEN the candle-annotator service starts
  • THEN the DATABASE_URL environment variable uses ${POSTGRES_PASSWORD} interpolation: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}

Scenario: Restart policy

  • WHEN container crashes or stops
  • THEN docker-compose automatically restarts container unless explicitly stopped (restart: unless-stopped)

Scenario: No SQLite volume

  • WHEN docker-compose.yml is parsed
  • THEN there is no candle-data volume defined or mounted

Scenario: PostgreSQL port bound to localhost only

  • WHEN docker-compose up runs
  • THEN the postgres service port mapping is 127.0.0.1:5432:5432 (not 5432:5432)

Scenario: MLflow port bound to localhost only

  • WHEN docker-compose up runs
  • THEN the mlflow service port mapping is 127.0.0.1:5000:5000

Scenario: ML service port bound to localhost only

  • WHEN docker-compose up runs
  • THEN the ml-service port mapping is 127.0.0.1:8001:8001

Scenario: Credentials via env var interpolation

  • WHEN docker-compose.yml is parsed
  • THEN all database credentials use ${POSTGRES_USER}, ${POSTGRES_PASSWORD}, and ${POSTGRES_DB} variable interpolation from .env

Requirement: Environment variable configuration

The project SHALL use environment variables for runtime configuration.

Scenario: .env.example file with placeholder credentials

  • WHEN repository is cloned
  • THEN .env.example contains POSTGRES_PASSWORD=change_me_to_a_strong_password (not a real password)

Scenario: .env file gitignored

  • WHEN .gitignore is inspected
  • THEN it includes .env (not just .env*.local)

Scenario: DATABASE_URL configuration

  • WHEN DATABASE_URL environment variable is set
  • THEN the Next.js application connects to the PostgreSQL database at the specified URL

Scenario: No DATABASE_PATH variable

  • WHEN environment variables are inspected
  • THEN there is no DATABASE_PATH variable (SQLite path is removed)

Scenario: PORT configuration

  • WHEN PORT environment variable is set
  • THEN Next.js server listens on specified port (default: 3000)

Scenario: NODE_ENV configuration

  • WHEN NODE_ENV environment variable is set to 'production'
  • THEN Next.js runs in production mode with optimizations enabled

Scenario: API_KEY configuration

  • WHEN API_KEY environment variable is set
  • THEN both Next.js middleware and FastAPI dependency use this key for authentication

Requirement: Container security

The Docker setup SHALL follow security best practices.

Scenario: Non-root user

  • WHEN container runs
  • THEN application process runs as non-root user 'appuser' (UID 1000)

Scenario: Read-only filesystem where possible

  • WHEN container runs
  • THEN only /app/data directory requires write permissions, all other files are read-only to appuser

Scenario: No sensitive data in image

  • WHEN Docker image is built
  • THEN .env files, secrets, and database files are not included in image layers

Scenario: Minimal attack surface

  • WHEN container runs
  • THEN only port 3000 is exposed, no SSH, no unnecessary services, alpine base reduces package vulnerabilities

Scenario: No node_modules in production image

  • WHEN the Next.js production Docker image is built
  • THEN the COPY --from=builder /app/node_modules line is removed (standalone output bundles needed deps)

Requirement: Production build optimization

The Docker image SHALL be optimized for production use with minimal size.

Scenario: Use alpine base images

  • WHEN Dockerfile specifies base images
  • THEN uses node:18-alpine for both build and runtime stages

Scenario: Multi-stage build cleanup

  • WHEN Docker image is built
  • THEN build artifacts, devDependencies, and source files are not included in final image

Scenario: Layer caching optimization

  • WHEN Dockerfile is structured
  • THEN package.json and package-lock.json are copied and dependencies installed before source code copy for better layer caching

Scenario: Final image size

  • WHEN Docker image build completes
  • THEN final image size is under 200MB (excluding data volume)

Scenario: Base images pinned to digest

  • WHEN Dockerfiles specify base images
  • THEN images use @sha256:<hash> pinning for reproducible builds