docs(pp-58): define deployable runtime image contract

This commit is contained in:
copilotcoder
2026-06-19 10:27:24 -04:00
parent 48a37b943f
commit aaa94f5d32
8 changed files with 327 additions and 194 deletions

View File

@@ -1,141 +0,0 @@
name: CICD
on:
workflow_dispatch:
env:
GITEA_REGISTRY_HOST: kankali.darkhelm.lan
GITEA_REGISTRY_IP: 10.18.75.2
concurrency:
group: cicd-launch-${{ github.ref }}
cancel-in-progress: true
jobs:
launch:
name: Launch CICD Start
# Use the same stable runner pool as the rest of CICD.
runs-on: ubuntu-act
timeout-minutes: 8
steps:
- name: Configure registry host resolution
run: |
if ! grep -q "${GITEA_REGISTRY_HOST}" /etc/hosts; then
echo "${GITEA_REGISTRY_IP} ${GITEA_REGISTRY_HOST}" >> /etc/hosts
fi
- name: Dispatch CICD Start workflow
env:
ACTIONS_TRIGGER_TOKEN: ${{ secrets.ACTIONS_TRIGGER_TOKEN }}
PACKAGE_ACCESS_TOKEN: ${{ secrets.PACKAGE_ACCESS_TOKEN }}
REPO_FULL: ${{ github.repository }}
HEAD_REF: ${{ github.head_ref }}
REF_NAME: ${{ github.ref_name }}
HEAD_SHA: ${{ github.sha }}
run: |
set -e
DISPATCH_TOKEN="${ACTIONS_TRIGGER_TOKEN:-${PACKAGE_ACCESS_TOKEN:-}}"
if [ -z "${DISPATCH_TOKEN}" ]; then
echo "❌ Missing dispatch token. Set ACTIONS_TRIGGER_TOKEN (repo write scope) or ensure PACKAGE_ACCESS_TOKEN has Actions workflow-dispatch permissions."
exit 1
fi
if ! grep -q "${GITEA_REGISTRY_HOST}" /etc/hosts; then
echo "${GITEA_REGISTRY_IP} ${GITEA_REGISTRY_HOST}" >> /etc/hosts
fi
REPO_OWNER="${REPO_FULL%/*}"
REPO_NAME="${REPO_FULL#*/}"
TARGET_REF="${HEAD_REF:-${REF_NAME}}"
TRACE_ID="cicd-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}-${HEAD_SHA:0:8}"
echo "trace_id=${TRACE_ID}"
echo "target_ref=${TARGET_REF}"
CANDIDATE_API_BASES=()
if [ -n "${GITHUB_SERVER_URL:-}" ]; then
CANDIDATE_API_BASES+=("${GITHUB_SERVER_URL%/}/api/v1")
fi
CANDIDATE_API_BASES+=("http://${GITEA_REGISTRY_IP}:3001/api/v1")
CANDIDATE_API_BASES+=("http://${GITEA_REGISTRY_HOST}:3001/api/v1")
ensure_curl() {
if command -v curl >/dev/null 2>&1; then
return 0
fi
if command -v apt-get >/dev/null 2>&1; then
export DEBIAN_FRONTEND=noninteractive
apt-get update -qq
apt-get install -y -qq curl ca-certificates
fi
command -v curl >/dev/null 2>&1
}
ensure_curl || { echo "❌ curl unavailable for dispatch"; exit 1; }
HELPER_PATH="/tmp/dispatch-workflow.sh"
fetch_dispatch_helper() {
local helper_ref="$1"
local api_base
for api_base in "${CANDIDATE_API_BASES[@]}"; do
helper_url="${api_base}/repos/${REPO_OWNER}/${REPO_NAME}/raw/scripts/dispatch-workflow.sh?ref=${helper_ref}"
if curl -fsS --connect-timeout 5 --max-time 20 \
-H "Authorization: token ${DISPATCH_TOKEN}" \
-H "User-Agent: plex-playlist-cicd" \
-o "${HELPER_PATH}" \
"${helper_url}"; then
chmod +x "${HELPER_PATH}"
return 0
fi
done
return 1
}
if ! fetch_dispatch_helper "${TARGET_REF}" && ! fetch_dispatch_helper "${HEAD_SHA}"; then
echo "❌ Failed to fetch scripts/dispatch-workflow.sh from repository"
exit 1
fi
DISPATCH_ARGS=(
--token "${DISPATCH_TOKEN}"
--repo "${REPO_FULL}"
--workflow "cicd-start.yaml"
--ref "${TARGET_REF}"
--head-sha "${HEAD_SHA}"
--source-workflow "CICD"
--trace-id "${TRACE_ID}"
)
for API_BASE in "${CANDIDATE_API_BASES[@]}"; do
DISPATCH_ARGS+=(--api-base "${API_BASE}")
done
"${HELPER_PATH}" "${DISPATCH_ARGS[@]}"
- name: Failure diagnostics
if: failure()
run: |
echo "=== Failure Diagnostics ==="
date -u '+timestamp_utc=%Y-%m-%dT%H:%M:%SZ'
echo "runner_name=${RUNNER_NAME:-unknown}"
echo "runner_hostname=${HOSTNAME:-unknown}"
uname -a || true
cat /etc/os-release 2>/dev/null || true
df -h || true
free -h || true
ps aux --sort=-%mem | head -n 30 || true
if command -v docker >/dev/null 2>&1; then
echo "=== Docker Diagnostics ==="
docker version || true
docker info || true
docker ps -a || true
docker images --digests | head -n 50 || true
else
echo "docker not available on this runner"
fi
echo "=== Kernel Tail ==="
dmesg | tail -n 120 || true

104
README.md
View File

@@ -9,6 +9,17 @@ A full-stack application for managing Plex playlists with a FastAPI backend and
- **Database**: PostgreSQL 16
- **Containerization**: Docker + Docker Compose
## Deployable Runtime Contract
Deployable image requirements are defined in:
- [docs/DEPLOYABLE_RUNTIME_CONTRACT.md](docs/DEPLOYABLE_RUNTIME_CONTRACT.md)
- [docs/adr/ADR003-deployable_runtime_image_contract.md](docs/adr/ADR003-deployable_runtime_image_contract.md)
These documents define backend/frontend runtime boundaries, startup and health
behavior expectations, environment contracts, and disallowed non-runtime
tooling classes in deployable artifacts.
## Development Setup
### Prerequisites
@@ -22,20 +33,24 @@ A full-stack application for managing Plex playlists with a FastAPI backend and
This project uses comprehensive linting and formatting:
**Backend (Python):**
- `ruff` - Fast Python linter and formatter
- `pyright` - Type checking
- `pydoclint` - Docstring linting (Google style)
**Frontend (TypeScript/Vue):**
- `eslint` - Linting with Vue and TypeScript support
- `prettier` - Code formatting
- `vue-tsc` - Vue TypeScript checking
- `eslint-plugin-tsdoc` - TSDoc documentation linting
**Task Runner:**
- `poethepoet` - Unified task runner for development workflows
**General:**
- `pre-commit` - Git hooks for automated quality checks
- TOML formatting and validation
@@ -66,45 +81,49 @@ pre-commit install
### Quick Start
1. **Clone the repository**
Clone the repository, then choose one of these startup paths.
2. **Unified Development (Recommended):**
```bash
# Complete setup and start development environment
poe setup
#### Unified Development
# Or manually:
cd backend
pip install -e .
poe dev-env-start
```
```bash
# Complete setup and start development environment
poe setup
3. **Traditional Setup:**
```bash
# Backend development
cd backend
pip install -e .
# Or manually:
cd backend
pip install -e .
poe dev-env-start
```
# Frontend development
cd frontend
npm install
npm run dev
```
#### Traditional Setup
4. **Docker Development:**
```bash
# Using Poe (recommended)
poe docker-dev-up
```bash
# Backend development
cd backend
pip install -e .
# Or directly
docker compose -f compose.dev.yml up --build
```
# Frontend development
cd frontend
npm install
npm run dev
```
5. **Production Build:**
```bash
poe docker-prod-up
# Or: docker compose up --build
```
#### Docker Development
```bash
# Using Poe (recommended)
poe docker-dev-up
# Or directly
docker compose -f compose.dev.yml up --build
```
#### Production Build
```bash
poe docker-prod-up
# Or: docker compose up --build
```
### Running in Production Mode
@@ -113,13 +132,14 @@ docker compose up --build -d
```
This will start:
- PostgreSQL database on port 5432
- FastAPI backend on port 8000
- Vue.js frontend on port 80
## Project Structure
```
```text
plex-playlist/
├── backend/ # FastAPI backend
├── frontend/ # Vue.js frontend
@@ -144,16 +164,20 @@ For full troubleshooting context, see `docs/GITEA_ACTIONS_TROUBLESHOOTING.md`.
## Environment Variables
### Backend
- `DATABASE_URL`: PostgreSQL connection string
- `BACKEND_REQUIRED_PYTHON`: Runtime policy baseline (`3.14` default)
- `ENVIRONMENT`: `development` or `production`
- `RELOAD`: Enable uvicorn auto-reload (development only)
### Frontend
- `NODE_ENV`: `development` or `production`
- No required runtime environment variables for production nginx serving
## Database
The PostgreSQL database is configured with:
- Database: `plex_playlist`
- User: `plex_user`
- Password: `plex_password`
@@ -168,12 +192,13 @@ The PostgreSQL database is configured with:
## API Documentation
When running, the FastAPI automatic documentation is available at:
- Development: http://localhost:8001/docs
- Production: http://localhost:8000/docs
- Development: <http://localhost:8001/docs>
- Production: <http://localhost:8000/docs>
---
# Manual Setup (if not using Docker)
## Manual Setup (if not using Docker)
## Backend Setup (FastAPI, Python 3.14, uv, ruff, pyright)
@@ -227,6 +252,7 @@ npm install
### 2. Recommended: Enable strictest TypeScript settings
Edit `tsconfig.json` and set:
```json
{
"compilerOptions": {
@@ -255,8 +281,11 @@ npm run dev
## Documentation
### Development & Workflow
- **[Development Environment Setup](docs/DEVELOPMENT.md)** - Comprehensive guide for setting up your development environment, git workflow, pre-commit hooks, manual tool usage, and CI/CD pipeline understanding
- **[Poe Task Reference](docs/POE_TASK_REFERENCE.md)** - Complete guide to unified development tasks and workflows using Poe the Poet
- **[Deployable Runtime Contract](docs/DEPLOYABLE_RUNTIME_CONTRACT.md)** - Canonical backend/frontend deployable runtime image requirements and exclusions
- **[ADR003: Deployable Runtime Image Contract Boundaries](docs/adr/ADR003-deployable_runtime_image_contract.md)** - Architectural decision that locks deployable image boundary policy
### Architecture & CI/CD
@@ -267,7 +296,6 @@ npm run dev
### Dependency Management & Automation
- **[Renovate Bot Setup](docs/RENOVATE_SETUP_GUIDE.md)** - Automated dependency updates with Renovate for Python, Node.js, and Docker dependencies
- **[Gitea API Token Setup](docs/GITEA_TOKEN_SETUP.md)** - Step-by-step guide for creating organization API tokens with proper permissions
### Operations & Troubleshooting

View File

@@ -129,8 +129,10 @@ jobs:
```
### Responsibility Split
- `.gitea/workflows/cicd-base.yml` owns base publication and verification.
- `.gitea/workflows/cicd.yml` owns complete-image build, tests, and deployment checks.
- `.gitea/workflows/docker-build-base.yaml` owns base publication and verification.
- `.gitea/workflows/docker-build-main.yaml` owns complete-image publication.
- `.gitea/workflows/cicd-start.yaml`, `.gitea/workflows/cicd-checks.yaml`, and
`.gitea/workflows/cicd-tests.yaml` own CI validation and checks.
- Main CI never rebuilds the base image locally.
## Local Development

View File

@@ -114,7 +114,7 @@ Firefox: browserType.launch: Executable doesn't exist
#### Docker Registry Operations
```yaml
# .gitea/workflows/cicd.yml
# .gitea/workflows/cicd-checks.yaml
- name: Login to Container Registry (with retry)
run: |
for attempt in {1..5}; do

View File

@@ -0,0 +1,156 @@
# Deployable Runtime Image Contract
## Purpose
Define the minimum deployable runtime contract for backend and frontend images.
This document is the canonical source for what must be present, what must not
be present, and what behavior deployment environments can rely on.
This contract supports issue PP-58 and establishes a baseline for future
automation work under epic #66.
## Scope
Included:
- Backend deployable runtime image requirements.
- Frontend deployable runtime image requirements.
- Runtime entrypoint, ports, health behavior, startup behavior, and environment
contracts.
- Explicitly disallowed non-runtime tooling classes in deployable images.
Excluded:
- CI workflow rewiring.
- Test execution redesign.
- New runtime hardening implementations not required to define contract.
## Backend Runtime Contract
### Runtime Artifact Definition
- Container build source: `Dockerfile.backend`.
- Runtime base image: `python:3.14-slim`.
- Runtime process: `uvicorn main:app --host 0.0.0.0 --port 8000`.
- Exposed runtime port: `8000`.
### Required Runtime Dependencies
The runtime artifact must include versions compatible with:
- `fastapi==0.120.2`
- `psycopg==3.2.12`
- `sqlalchemy==2.0.44`
- `uvicorn==0.38.0`
The lockfile in `backend/uv.lock` is the dependency source of truth.
### Required Runtime Environment Contract
- `DATABASE_URL` is required.
- Accepted form: `postgresql://...` or SQLAlchemy async form.
- Runtime normalization to async psycopg dialect is performed in
`backend/src/backend/database.py`.
- `BACKEND_REQUIRED_PYTHON` is optional and defaults to `3.14`.
### Backend Health and Startup Behavior
- Startup must fail fast if runtime policy checks fail.
- Source: `backend/src/backend/main.py` lifecycle (`lifespan`) validation.
- Health endpoint contract:
- `GET /health` returns `200` with `{"status":"healthy","database":"connected"}`
when database probe succeeds.
- `GET /health` returns `503` with
`{"status":"unhealthy","database":"disconnected"}` when probe fails.
- Compatibility diagnostics endpoint:
- `GET /compatibility` reports policy and package compatibility status.
### Backend Runtime Checklist
- [ ] Runtime image built from `Dockerfile.backend`.
- [ ] Runtime process is uvicorn serving `main:app` on `0.0.0.0:8000`.
- [ ] `DATABASE_URL` is set in deployment runtime.
- [ ] `GET /health` behavior matches contract.
- [ ] Startup fails on runtime policy mismatch.
- [ ] Deployable artifact excludes CI-only and test-only tooling classes.
## Frontend Runtime Contract
### Frontend Runtime Artifact Definition
- Container build source: `Dockerfile.frontend` (target `production`).
- Runtime base image: `nginx:alpine`.
- Runtime process: `nginx -g "daemon off;"`.
- Exposed runtime port: `80`.
- Runtime artifact payload: static assets from `/app/dist` copied to
`/usr/share/nginx/html`.
### Frontend Runtime Entry and Routing Contract
- Nginx configuration source: `frontend/nginx.conf`.
- SPA routing behavior must use fallback to `index.html` for unknown routes.
- API requests under `/api/` are proxied to backend service endpoint
`http://backend:8000/` in compose deployments.
### Frontend Health and Startup Behavior
- Startup expectation: nginx process starts and serves static assets on port 80.
- Runtime health expectation for deployment checks:
- `GET /` should return `200` and serve frontend entry document.
- API proxy readiness is dependent on backend runtime availability.
### Frontend Runtime Environment Contract
- No required runtime environment variables are defined for nginx static serving.
- Build-time frontend mode is production-oriented and not part of runtime env
contract.
### Frontend Runtime Checklist
- [ ] Runtime image built from `Dockerfile.frontend` production target.
- [ ] Runtime process is nginx serving on port 80.
- [ ] SPA route fallback behavior is present.
- [ ] `/api/` proxy behavior aligns with backend service wiring.
- [ ] Deployable artifact excludes CI-only and test-only tooling classes.
## Disallowed Tooling Classes in Deployable Runtime Images
Deployable runtime artifacts must not include tooling classes that are only
needed for CI, validation, or local development workflows.
Disallowed classes:
- Linters and formatters (example: ruff, eslint, prettier).
- Type checking and static analysis tooling (example: pyright, vue-tsc).
- Test frameworks and test drivers (example: pytest, vitest, playwright).
- Browser test binaries and CI runner helper tools.
- Build-only package managers and build toolchains not needed at runtime.
Note:
- Multi-stage builds may use these tools in build stages.
- These tools must not be required by or present in final deployable runtime
image layers.
## Acceptance Criteria Traceability (PP-58)
1. Backend runtime requirements are documented and approved.
- Covered by: "Backend Runtime Contract" and backend checklist.
2. Frontend runtime requirements are documented and approved.
- Covered by: "Frontend Runtime Contract" and frontend checklist.
3. Runtime contracts include health endpoint expectations and startup behavior.
- Covered by: backend health/startup section and frontend health/startup
section.
4. Non-runtime tool classes are explicitly excluded from deployable image
definition.
- Covered by: "Disallowed Tooling Classes in Deployable Runtime Images".
## Future Enforcement Hooks (Out of Scope for PP-58)
Potential follow-up automation under epic #66:
- Policy checks validating final image layers do not include disallowed tooling
classes.
- Contract tests that assert documented health/startup behavior.
- CI checks that verify Dockerfile target boundaries remain aligned with this
contract.

View File

@@ -19,6 +19,24 @@ This document outlines how to set up your development environment and work with
- **[CI/CD Multi-Stage Build Architecture](CICD_MULTI_STAGE_BUILD.md)** - Technical details of the optimized build system
- **[CI/CD Troubleshooting](GITEA_ACTIONS_TROUBLESHOOTING.md)** - Common issues and solutions
- **[Secure Docker CI/CD](SECURE_DOCKER_CICD.md)** - Security considerations and practices
- **[Deployable Runtime Contract](DEPLOYABLE_RUNTIME_CONTRACT.md)** - Canonical backend/frontend runtime artifact contract and exclusion rules
- **[ADR003: Deployable Runtime Image Contract Boundaries](adr/ADR003-deployable_runtime_image_contract.md)** - Decision record for deployable runtime boundaries
## Deployable Runtime Artifacts
When changing deployment behavior or image composition, treat
`DEPLOYABLE_RUNTIME_CONTRACT.md` as the source of truth for:
- Runtime entrypoint and exposed ports.
- Health and startup expectations.
- Runtime environment contract.
- Disallowed non-runtime tooling classes in final deployable images.
Scope boundary:
- This repository separates contract definition from enforcement mechanics.
- CI workflow rewiring and test execution redesign are out of scope for PP-58
and belong to follow-up work under epic #66.
## Quick Start
@@ -69,10 +87,10 @@ docker compose -f compose.dev.yml up -d --build
### Service Access
- **Frontend**: http://localhost:3000
- **Backend API**: http://localhost:8000
- **API Documentation**: http://localhost:8000/docs
- **Database**: localhost:5432 (user: `plex`, password: see `secrets/postgres_password`)
- **Frontend**: <http://localhost:3000>
- **Backend API**: <http://localhost:8000>
- **API Documentation**: <http://localhost:8000/docs>
- **Database**: `localhost:5432` (user: `plex`, password: see `secrets/postgres_password`)
## Poe the Poet Task Runner
@@ -197,6 +215,7 @@ pre-commit run --all-files
### How It Works
Pre-commit automatically runs on every `git commit` and will:
- Format code (Prettier, Ruff)
- Check syntax (ESLint, Pyright)
- Validate files (YAML, TOML, trailing whitespace)
@@ -207,6 +226,7 @@ If any hook fails, the commit is blocked until issues are fixed.
### Pre-commit is Optional
While strongly recommended, pre-commit is not required because:
- **CI Validation**: All the same checks run in CI
- **Developer Choice**: Some prefer manual tool usage
- **Learning**: Developers can run tools individually to understand them
@@ -221,7 +241,8 @@ If you prefer not to use pre-commit, here's how to run each tool manually:
Navigate to the `backend/` directory for all backend commands.
#### Code Formatting
#### Backend Code Formatting
```bash
# Format code with Ruff
uv run ruff format .
@@ -230,13 +251,15 @@ uv run ruff format .
uv run ruff check . --fix
```
#### Type Checking
#### Backend Type Checking
```bash
# Run Pyright type checker
uv run pyright
```
#### Testing
#### Backend Testing
```bash
# Run unit tests
uv run pytest
@@ -251,7 +274,8 @@ uv run pytest tests/integration/
uv run xdoctest src/
```
#### Documentation
#### Backend Documentation
```bash
# Check docstring style
uv run pydoclint --config=pyproject.toml src/
@@ -261,7 +285,8 @@ uv run pydoclint --config=pyproject.toml src/
Navigate to the `frontend/` directory for all frontend commands.
#### Code Formatting
#### Frontend Code Formatting
```bash
# Format code with Prettier
yarn format
@@ -271,6 +296,7 @@ yarn format:check
```
#### Linting
```bash
# Run ESLint
yarn lint
@@ -282,13 +308,15 @@ yarn lint:fix
yarn lint:tsdoc
```
#### Type Checking
#### Frontend Type Checking
```bash
# Run Vue TypeScript compiler
yarn type-check
```
#### Testing
#### Frontend Testing
```bash
# Run unit tests
yarn test
@@ -341,22 +369,26 @@ The CI/CD pipeline uses a **multi-stage build architecture** for optimal perform
- **Stage 2**: Build complete image (project code and dependencies) - **rebuilt every time**
Pipeline triggers:
- Push to any branch
- Pull requests to `main` or `develop`
### Multi-Stage Build Benefits ✅ **VALIDATED SUCCESSFUL**
**Performance Gains**:
- **85% build time improvement**: 3-5 minutes (down from 15-25 minutes)
- Base image cached when `Dockerfile.cicd-base` unchanged (~95% of runs)
- **100% success rate** achieved with optimized dependency management
- Raspberry Pi 4GB workers handle builds efficiently with resource optimization
**Architecture**:
- `cicd-base:latest` - System dependencies (Python 3.14, Node.js 24, build tools, pre-installed dev packages)
- `cicd:latest` - Complete environment (project code + optimized dependency installation)
**Recent Optimizations** (November 2025):
- **Dependency-first build pattern** prevents cache invalidation on code changes
- **Yarn PnP state regeneration** ensures reliable frontend builds
- **Network-resilient E2E testing** with simplified Docker operations

View File

@@ -29,7 +29,7 @@ RUN --mount=type=secret,id=ssh_private_key \
## 🏗️ CI/CD Pipeline Implementation
### Gitea Actions Workflow
The `.gitea/workflows/cicd.yml` file now uses:
The CI workflow files under `.gitea/workflows/` now use:
1. **Docker BuildKit Enabled**
```yaml

View File

@@ -0,0 +1,56 @@
# ADR003: Deployable Runtime Image Contract Boundaries
- Status: Accepted
- Date: 2026-06-19
## Context
The repository has both deployable runtime artifacts and CI/development tooling
artifacts. Without an explicit boundary, non-runtime concerns can drift into
deployable images, making runtime behavior less predictable and increasing
artifact complexity.
Issue PP-58 requires a clear, approved contract for minimal backend and frontend
deployable images, including health and startup behavior and explicit exclusion
of non-runtime tooling classes.
## Decision
Adopt a canonical deployable runtime contract at:
- `docs/DEPLOYABLE_RUNTIME_CONTRACT.md`
The contract defines, for backend and frontend deployable images:
1. Runtime artifact boundaries (final image intent and payload).
2. Runtime entrypoint and exposed ports.
3. Runtime health and startup behavior expectations.
4. Runtime environment variable contract.
5. Disallowed non-runtime tooling classes in final deployable images.
Scope guardrails for PP-58:
- Documentation and architectural decision codification only.
- No CI workflow rewiring in this issue.
- No test execution redesign in this issue.
- Enforcement automation deferred to follow-up work under epic #66.
## Consequences
Positive:
- Deployable image intent is explicit and auditable.
- Future tickets can implement automated checks against a stable policy.
- Reduced ambiguity between runtime artifacts and CI/development environments.
Negative:
- Requires ongoing documentation maintenance when runtime contracts evolve.
- Drift can still occur if future changes bypass policy review.
## Alternatives Considered
- Rely on Dockerfiles only as implicit contract.
- Rejected: too easy for intent drift and inconsistent interpretation.
- Enforce contract immediately in CI without documentation-first baseline.
- Rejected: increases implementation risk without agreed policy language.