From 8a5f28e3bb289444fae9ce888a046d61aa692c39 Mon Sep 17 00:00:00 2001 From: medusa Date: Tue, 5 Aug 2025 23:33:05 -0500 Subject: [PATCH] Update tech_docs/docker_compose_guide.md --- tech_docs/docker_compose_guide.md | 336 ++++++++++++++++++++++++++++++ 1 file changed, 336 insertions(+) diff --git a/tech_docs/docker_compose_guide.md b/tech_docs/docker_compose_guide.md index c1f74e7..2bd99fd 100644 --- a/tech_docs/docker_compose_guide.md +++ b/tech_docs/docker_compose_guide.md @@ -1,3 +1,339 @@ +Here is a **drop-in Jinja2 template skeleton** that turns the entire service stanza into variables you can override at render-time (`docker compose -f <(jinja2 compose.yml.j2 env.yml)` or `docker compose config`). +You only need to set the variables you care about; everything else falls back to safe blanks or defaults. + +```jinja2 +# compose.yml.j2 +services: + {{ service_name | default("app") }}: + # — Image or Build (mutually exclusive; image wins if provided) — +{% if image is defined %} + image: {{ image }} +{% elif build is defined %} + build: + context: {{ build.context | default(".") }} + dockerfile: {{ build.dockerfile | default("Dockerfile") }} +{% endif %} + + # — Ports — +{% if ports is defined %} + ports: +{% for p in ports %} + - "{{ p }}" +{% endfor %} +{% endif %} + + # — Environment — +{% if environment is defined %} + environment: +{% for k,v in environment.items() %} + {{ k }}: {{ v }} +{% endfor %} +{% endif %} + + # — Volumes / Mounts — +{% if volumes is defined %} + volumes: +{% for v in volumes %} + - "{{ v }}" +{% endfor %} +{% endif %} + + # — Dependencies — +{% if depends_on is defined %} + depends_on: +{% for d in depends_on %} + - "{{ d }}" +{% endfor %} +{% endif %} + + # — Health Check — +{% if healthcheck is defined %} + healthcheck: +{% for k,v in healthcheck.items() %} + {{ k }}: {{ v }} +{% endfor %} +{% endif %} + + # — User / Privileges — + user: {{ user | default("") }} +{% if cap_add is defined %} + cap_add: +{% for c in cap_add %} + - {{ c }} +{% endfor %} +{% endif %} +{% if cap_drop is defined %} + cap_drop: +{% for c in cap_drop %} + - {{ c }} +{% endfor %} +{% endif %} +{% if security_opt is defined %} + security_opt: +{% for s in security_opt %} + - {{ s }} +{% endfor %} +{% endif %} + + # — Restart Policy — + restart: {{ restart | default("unless-stopped") }} + + # — Networks — +{% if networks is defined %} + networks: +{% for n in networks %} + - {{ n }} +{% endfor %} +{% endif %} + + # — Resource Limits / Placement — +{% if deploy is defined %} + deploy: +{% for k,v in deploy.items() %} + {{ k }}: {{ v }} +{% endfor %} +{% endif %} + + # — Logging — +{% if logging is defined %} + logging: + driver: {{ logging.driver | default("json-file") }} +{% if logging.options is defined %} + options: +{% for k,v in logging.options.items() %} + {{ k }}: {{ v }} +{% endfor %} +{% endif %} +{% endif %} + + # — Misc (blank if unset) — + command: {{ command | default("") | tojson if command else "" }} + entrypoint: {{ entrypoint | default("") | tojson if entrypoint else "" }} + container_name: {{ container_name | default("") }} + hostname: {{ hostname | default("") }} + working_dir: {{ working_dir | default("") }} + tty: {{ tty | default(false) | lower }} + stdin_open: {{ stdin_open | default(false) | lower }} + privileged: {{ privileged | default(false) | lower }} + init: {{ init | default(false) | lower }} + stop_grace_period: {{ stop_grace_period | default("10s") }} + stop_signal: {{ stop_signal | default("SIGTERM") }} +{% if tmpfs is defined %} + tmpfs: +{% for t in tmpfs %} + - {{ t }} +{% endfor %} +{% endif %} +{% if labels is defined %} + labels: +{% for k,v in labels.items() %} + {{ k }}: {{ v }} +{% endfor %} +{% endif %} +``` + +Usage example with a tiny `env.yml`: + +```yaml +# env.yml +service_name: api +image: mycorp/api:1.4.2 +ports: + - "8080:8080" +environment: + LOG_LEVEL: info + DB_URL: postgresql://db:5432/app +volumes: + - ./data:/app/data +depends_on: + - db +restart: always +``` + +Render and validate: + +```bash +jinja2 compose.yml.j2 env.yml | docker compose -f - config # dry-run +``` + +All other knobs (secrets, configs, devices, ulimits, etc.) can be added the same way—only when you need them. + +--- + +Below is the minimal, copy-paste-ready workflow that gives you: + +- **instant feedback** while you type (IDE) +- **CI gates** that reject bad templates (GitHub Actions / pre-commit) +- **no false positives** from YAML or Docker linters that don’t understand Jinja + +-------------------------------------------------------- +1. Install +-------------------------------------------------------- +```bash +# 1.1 CLI linter (fast, zero-config) +pip install git+https://github.com/aristanetworks/j2lint.git # + +# 1.2 Optional VS Code inline squiggles +code --install-extension samuelcolvin.jinjahtml # +``` + +-------------------------------------------------------- +2. Local lint (CLI) +-------------------------------------------------------- +```bash +# single file +j2lint compose.yml.j2 + +# whole directory, custom extensions, exit non-zero on error +j2lint templates/ --extensions j2,yaml --log --json +``` + +-------------------------------------------------------- +3. Pre-commit hook (repo-level gate) +-------------------------------------------------------- +Create `.pre-commit-config.yaml`: + +```yaml +repos: + - repo: https://github.com/aristanetworks/j2lint + rev: v1.1.0 # pick latest tag + hooks: + - id: j2lint + args: ["--"] # keep pre-commit from treating other files as args +``` + +Then: + +```bash +pip install pre-commit +pre-commit install # runs on every `git commit` +pre-commit run --all-files # one-shot check +``` + +-------------------------------------------------------- +4. CI example (GitHub Action) +-------------------------------------------------------- +`.github/workflows/lint-jinja.yml` + +```yaml +name: Lint Jinja2 +on: [push, pull_request] +jobs: + j2lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: { python-version: "3.11" } + - run: pip install j2lint + - run: j2lint templates/ --json +``` + +-------------------------------------------------------- +5. Render-time *syntax* check (fail fast) +-------------------------------------------------------- +If you just want to know “is the Jinja valid?” without any style rules: + +```bash +python - <<'PY' +import sys, os +from jinja2 import Environment, FileSystemLoader, TemplateSyntaxError +env = Environment() +for root, _, files in os.walk("templates"): + for f in files: + if f.endswith(".j2"): + try: + env.parse(open(os.path.join(root, f)).read()) + except TemplateSyntaxError as e: + print(f"{f}: {e}") + sys.exit(1) +PY +``` + +This doubles as a unit test you can add to pytest. + +-------------------------------------------------------- +6. VS Code one-shot tasks.json (optional) +-------------------------------------------------------- +`.vscode/tasks.json` + +```json +{ + "label": "j2lint", + "type": "shell", + "command": "j2lint", + "args": ["${file}"], + "group": "test", + "presentation": { "reveal": "always" } +} +``` + +Now ⇧⌘B → *j2lint* lints the file you’re editing. + +-------------------------------------------------------- +Summary +-------------------------------------------------------- +- **CLI**: `j2lint` +- **Editor**: VS Code extension +- **Gate**: pre-commit or GitHub Action using the same linter +- **Zero extra dependencies** beyond Python. + +--- + +Below is the canonical “complete-line” set for a **Compose v3+ service block**, minus anything you already listed. +Each item is one line you can drop into a YAML service stanza. + +``` +image: … +build: … +ports: … +environment: … +volumes: … +depends_on: … +healthcheck: … +user: … +deploy: … + +# --- the missing ones --- +command: # override CMD +entrypoint: # override ENTRYPOINT +container_name: # fixed name (beware singleton) +restart: # no | always | on-failure | unless-stopped +networks: # list of custom networks +dns: # override DNS servers +dns_search: # override DNS search domains +extra_hosts: # static /etc/hosts entries +hostname: # container hostname +domainname: # container NIS domain name +ipc: # ipc mode (shareable, private, host, container:…) +mac_address: # static MAC +privileged: # true/false (danger) +cap_add: # add Linux capabilities +cap_drop: # drop Linux capabilities +device_cgroup_rules: # e.g., ["c 116:* rmw"] +devices: # expose host devices +security_opt: # ["no-new-privileges:true"] +sysctls: # kernel tunables +ulimits: # {"nproc": 65535, "nofile": {"soft": 1024, "hard": 2048}} +working_dir: # override WORKDIR +labels: # arbitrary key/val metadata +logging: # driver + options +stop_grace_period: # SIGKILL timeout +stop_signal: # override STOPSIGNAL +stdin_open: # -i (keep STDIN open) +tty: # -t (allocate pseudo-TTY) +init: # run tini (true or path) +tmpfs: # tmpfs mounts list +secrets: # named secrets +configs: # named configs +isolation: # Windows isolation mode +platform: # linux/amd64, linux/arm64, etc. +``` + +If you can rattle off what every one of those does, the Compose surface is covered. + +--- + Learning-GOAL: “I can read, reason about and harden any Dockerfile or `docker run` incantation I meet—without drowning in trivia.” Below is a **minimal, language-agnostic curriculum** expressed as executable **pseudocode**.