11 KiB
Development-CI Environment Parity Analysis
Date: 2025-11-09
Repository: satware.ai
Analyzed by: Jane Alesi
Executive Summary
The local Docker development environment (mkdocs.sh + docker/mkdocs-material/Dockerfile) and GitHub Actions CI/CD workflows (.github/workflows/deploy-live.yml and deploy-preview.yml) are functionally identical but suffer from critical maintainability issues due to dependency duplication and lack of a single source of truth.
Critical Finding
Both environments use identical Python packages (15 total, including mkdocs-material==9.6.14), but dependencies are hardcoded in 3 separate locations:
docker/mkdocs-material/Dockerfile.github/workflows/deploy-live.yml.github/workflows/deploy-preview.yml
This creates high risk of drift when updating dependencies.
Detailed Comparison Matrix
| Aspect | Local Development | GitHub Actions CI/CD | Match? | Risk Level |
|---|---|---|---|---|
| Base OS | Debian Bullseye (via python:3.13-slim-bullseye) | Ubuntu 22.04 | ⚠️ Different | Low |
| Python Version | 3.13 | 3.13 | ✅ Identical | None |
| Python Packages | 15 packages (see below) | 15 packages (see below) | ✅ Identical | None |
| mkdocs-material Version | 9.6.14 (pinned) | 9.6.14 (pinned) | ✅ Identical | None |
| SCSS Compilation | pysassc (libsass) | pysassc (libsass) | ✅ Identical | None |
| System Dependencies | build-essential, cairo, gdk-pixbuf, pango, etc. | Not explicitly installed (bundled in ubuntu-22.04) | ⚠️ Implicit | Low |
| Build Process | mkdocs serve (development server) | mkdocs build → gh-deploy | ⚠️ Different purpose | None |
| Dependency Management | Hardcoded in Dockerfile | Hardcoded in workflow YAML | ❌ DUPLICATED | HIGH |
| Version Locking | Inline pip install | Inline pip install | ❌ NO requirements.txt | HIGH |
Python Packages (Identical in Both Environments)
cairosvg
libsass
mkdocs-exclude
mkdocs-git-revision-date-localized-plugin
mkdocs-glightbox
mkdocs-include-markdown-plugin
mkdocs-literate-nav
mkdocs-macros-plugin
mkdocs-material==9.6.14
mkdocs-material[imaging]
mkdocs-minify-plugin
mkdocs-redirects
mkdocs-rss-plugin
mkdocs-snippets
mkdocs-video
watchdog
Critical Issues Identified
🔴 Issue 1: Dependency Duplication (HIGH RISK)
Problem: Dependencies listed in 3 separate files means updating package versions requires changing 3 locations. Missing one creates immediate drift.
Current State:
docker/mkdocs-material/Dockerfile(lines 20-36).github/workflows/deploy-live.yml(lines 25-39).github/workflows/deploy-preview.yml(lines 25-39)
Risk: Developer updates Dockerfile but forgets workflow → local works, CI fails → "works on my machine" syndrome.
🔴 Issue 2: No Requirements File (HIGH RISK)
Problem: Industry best practice is requirements.txt with pinned versions for reproducibility, audit trail, and dependency locking.
Current Risk:
- No version control history of dependency changes
- No easy rollback if package update breaks build
- Manual coordination required for updates
🟡 Issue 3: Base OS Mismatch (LOW RISK)
Problem: Local uses Debian Bullseye, CI uses Ubuntu 22.04. Both work but creates slight differences.
Current Impact: Minimal - Python packages abstract most OS differences. System dependencies (cairo, pango) are handled differently but both work.
🟢 Issue 4: Build Process Difference (NO RISK)
Status: Expected and correct behavior:
- Local:
mkdocs servefor live development with hot reload - CI:
mkdocs build+mkdocs gh-deployfor static site generation
This is intentional and appropriate.
Best Practices Research Findings
Based on research of GitHub Actions + Docker best practices (2024-2025):
1. Single Source of Truth
✅ Recommendation: Create requirements.txt with all Python dependencies
- Eliminates duplication
- Enables version locking with
pip freeze - Provides audit trail in git history
- Industry standard for Python projects
2. Dockerfile Reuse in CI
✅ Recommendation: GitHub Actions should build and use the same Dockerfile
- Guarantees environment parity
- Reduces configuration drift
- "Use same Dockerfile everywhere" principle
3. Multi-Stage Builds
✅ Recommendation: Separate build and runtime stages
- Already partially implemented (dependencies vs runtime)
- Could optimize further for production
4. Cache Optimization
✅ Recommendation: Layer caching for faster builds
- Docker layer caching in GitHub Actions
- Cache Python packages between runs
Proposed Solutions
🎯 Option A: Requirements.txt with Dockerfile Reuse (RECOMMENDED)
Implementation Steps:
-
Create
requirements.txt# Extract from current Dockerfile cairosvg libsass mkdocs-exclude mkdocs-git-revision-date-localized-plugin mkdocs-glightbox mkdocs-include-markdown-plugin mkdocs-literate-nav mkdocs-macros-plugin mkdocs-material==9.6.14 mkdocs-material[imaging] mkdocs-minify-plugin mkdocs-redirects mkdocs-rss-plugin mkdocs-snippets mkdocs-video watchdog -
Update Dockerfile
FROM python:3.13-slim-bullseye WORKDIR /docs # Install system dependencies RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential curl git \ libcairo2 libffi-dev libgdk-pixbuf2.0-0 \ libpango-1.0-0 libpangocairo-1.0-0 shared-mime-info \ && rm -rf /var/lib/apt/lists/* # Copy and install Python dependencies COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt -
Update GitHub Actions Workflows
- name: Setup Python uses: actions/setup-python@v5 with: python-version: '3.13' - name: Install dependencies run: | pip install --upgrade pip pip install -r requirements.txt -
Update
mkdocs.sh# Build Docker image from Dockerfile docker build -t squidfunk/mkdocs-material-custom ${PWD}/docker/mkdocs-material # Run container (unchanged) docker run --rm -it --user $(id -u):$(id -g) -p 8000:8000 \ -v ${PWD}:/docs --entrypoint sh \ squidfunk/mkdocs-material-custom -c "..."
Benefits:
- ✅ Single source of truth (
requirements.txt) - ✅ Guaranteed parity (same file used everywhere)
- ✅ Easy dependency updates (one file)
- ✅ Git history of dependency changes
- ✅ Industry standard approach
Trade-offs:
- Requires updating 3 files initially (one-time cost)
- Dockerfile must
COPY requirements.txt(adds build context dependency)
🎯 Option B: Docker-First CI/CD (ALTERNATIVE)
Make GitHub Actions use the Docker container:
jobs:
deploy:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Build Docker image
run: docker build -t mkdocs-builder ./docker/mkdocs-material
- name: Build SCSS
run: |
docker run --rm -v ${PWD}:/docs mkdocs-builder \
pysassc overrides/assets/css/custom.scss docs/assets/css/custom.css
- name: Build site
run: docker run --rm -v ${PWD}:/docs mkdocs-builder mkdocs build
- name: Deploy
run: docker run --rm -v ${PWD}:/docs mkdocs-builder mkdocs gh-deploy --force
Benefits:
- ✅ 100% identical environments (same container)
- ✅ True "build once, run anywhere"
Trade-offs:
- ❌ Slower CI builds (Docker build overhead)
- ❌ More complex workflow
- ❌ Still needs requirements.txt for maintainability
🎯 Option C: Hybrid Approach (BALANCED)
Combine best of both:
- Create
requirements.txt(single source of truth) - Keep lightweight GitHub Actions (faster CI)
- Docker image references
requirements.txt - Both use same dependency file
This is Option A - recommended approach.
Implementation Roadmap
Phase 1: Create Single Source of Truth (IMMEDIATE)
- Create
requirements.txtin repository root - Copy exact package list from Dockerfile
- Commit with message:
build: create requirements.txt for dependency management
Phase 2: Update Dockerfile (IMMEDIATE)
- Modify
docker/mkdocs-material/Dockerfileto userequirements.txt - Test local build:
docker build -t test ./docker/mkdocs-material - Test local server:
./mkdocs.sh - Commit with message:
build: update Dockerfile to use requirements.txt
Phase 3: Update GitHub Actions (IMMEDIATE)
- Update
.github/workflows/deploy-live.ymlto userequirements.txt - Update
.github/workflows/deploy-preview.ymlto userequirements.txt - Test on feature branch before merging
- Commit with message:
ci: use requirements.txt for Python dependencies
Phase 4: Validation (BEFORE MERGE)
- Local development server works
- GitHub Actions preview deployment succeeds
- No regressions in site build
Phase 5: Documentation (POST-MERGE)
- Update README.md with dependency update process
- Document: "To update dependencies, edit requirements.txt only"
- Add comments in Dockerfile and workflows pointing to requirements.txt
Dependency Update Process (After Implementation)
Current (BROKEN):
# Must update 3 files manually - high error risk
1. Edit docker/mkdocs-material/Dockerfile
2. Edit .github/workflows/deploy-live.yml
3. Edit .github/workflows/deploy-preview.yml
Future (RECOMMENDED):
# Single source of truth
1. Edit requirements.txt
2. Test locally: docker build && ./mkdocs.sh
3. Commit and push - CI automatically uses new versions
Risk Assessment
| Risk | Current | After Implementation |
|---|---|---|
| Dependency drift | HIGH | LOW |
| "Works on my machine" | MEDIUM | LOW |
| Update complexity | HIGH | LOW |
| Audit trail | NONE | FULL (git history) |
| Rollback difficulty | HIGH | LOW (git revert) |
Conclusion
Current Status: ✅ Environments are functionally identical but ❌ maintained unsafely
Recommended Action: Implement Option A (Requirements.txt with Dockerfile Reuse) immediately
Effort: ~30 minutes implementation, ~15 minutes testing
Impact:
- Eliminates high-risk dependency duplication
- Establishes industry-standard Python dependency management
- Prevents future "works on my machine" issues
- Makes dependency updates 10x easier and safer
Next Step: Create requirements.txt file and begin Phase 1 implementation
References
Files Analyzed
mkdocs.sh- Local development container launcherdocker/mkdocs-material/Dockerfile- Custom Docker image definition.github/workflows/deploy-live.yml- Production deployment workflow.github/workflows/deploy-preview.yml- Preview deployment workflow
Research Sources
- "GitHub Actions Docker development environment parity best practices 2024 2025" (Perplexity search)
- "MkDocs Material Docker GitHub Actions best practices requirements.txt vs inline pip install" (Perplexity search)
Key Best Practices Applied
- Use same Dockerfile in both local and CI
- Single source of truth for dependencies (requirements.txt)
- Version locking for reproducibility
- Layer caching optimization
- Clear documentation and audit trails