Update tech_docs/docker_compose_guide.md
This commit is contained in:
@@ -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.”
|
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**.
|
Below is a **minimal, language-agnostic curriculum** expressed as executable **pseudocode**.
|
||||||
|
|||||||
Reference in New Issue
Block a user