# Ignition + Docker Development Framework ## What This Is Reusable scaffold for Ignition + Docker projects with Claude Code. The PLC I/O Testing Platform is the first project built on this framework. ## Stack - Ignition 8.3 (Git-native project storage, Perspective, WebDev, OPC-UA) - Rockwell Allen-Bradley ControlLogix/CompactLogix, Studio 5000, PlantPAx - Docker Compose on Ubuntu 24 (Ignition, PostgreSQL, Traefik, Modbus sims) - Gitea at 192.168.3.59:3000 for source control - Jython 2.7 inside Ignition; Python 3.10+ for external tooling - Claude Code on the Linux host (VS Code Remote to Docker host) - WikiJS at wikijs.primecontrols-dev.com for team documentation (`.md` files auto-sync on `git push` via `tools/publish_docs.py`) ## Gateway HTTP API Ignition 8.3 exposes a full REST API. Use it for infrastructure-as-code configuration — adding DB connections, OPC connections, importing tags, managing projects, etc. - **Browse**: `http://:8088/openapi` - **Spec**: `http://:8088/openapi.json` - **Auth header**: `X-Ignition-API-Token: :` - **Full reference**: `ignition/ignition-api.md` - **Skill**: `/ignition-configure` — resolves token and walks through config tasks ## API Key / Secrets Policy **Source of truth**: `~/.config/ignition-dev/secrets.env` (chmod 600, never committed) **Template**: `.env.example` in repo root — update this when adding new secrets - All secrets via environment variables — no hardcoded values in any file - Docker Compose references host env vars: `VAR=${VAR}` pattern - Python scripts use `python-dotenv` + `os.environ["KEY"]` (loud failure on missing) - `.env` at project root is a symlink to secrets file — listed in .gitignore - CI secrets live in Gitea repository secrets settings Key variables: `IGNITION_API_TOKEN`, `POSTGRES_PASSWORD` — see `.env.example` for full list. ## Language Rules ### Jython 2.7 (Inside Ignition) All scripts in `ignition/project/` run Jython 2.7. Top violations: - NO f-strings → use `"text {}".format(val)` - NO walrus operator `:=` - NO type hints → no `def foo(x: int) -> str:` Full constraint list: [ignition/CLAUDE.md](ignition/CLAUDE.md) ### Python 3.10+ (External Tooling) All scripts outside Ignition use Python 3.10+. Add at top of every file: ```python # env: python3.10+ (external tooling — NOT Ignition) ``` ## Safety Rules (Non-Negotiable) 1. **Validate JSON** before writing to any Ignition resource directory. 2. **Back up files** before overwriting: `cp file file.bak` 3. **Sorted-key JSON** for all JSON files (Git-friendly diffs). 4. **Never restart Ignition gateway** without explicit user confirmation. 5. **Never `docker compose down`** without confirmation (destroys unnamed volumes). 6. **Every change needs a verification step** — scenario run, curl, tag read. 7. **Scan after file edits** — after editing `ignition/project/` run `POST /data/api/v1/scan/projects`; after editing `docker/gw-config/` run `POST /data/api/v1/scan/config`. Use `/ignition-configure`. ## Canonical Patterns (Do Not Change Without Discussion) ### UDT / Linking Pattern Linking/Link sub-UDT with `LinkPath` + `LinkData` for PLC signal binding. Decouples simulation from PLC I/O structure. All device test tags follow this. ### JSON Test Scenarios Five required sections: `identity`, `device`, `initial_state`, `sequence`, `expected_outcomes`. See `testing/CLAUDE.md` for full schema. ### WebDev API All external I/O goes through WebDev endpoints — never direct tag file edits at runtime: `tagWrite`, `tagRead`, `tagBrowse`, `health`. See `webdev/CLAUDE.md`. ### Ignition as I/O Orchestrator Ignition writes to Modbus containers and PLC I/O tags. PLC runs real logic. Test runner talks to Ignition only. Never bypass Ignition to write directly to Modbus or PLC. ## Directory Layout ``` project-root/ ├── CLAUDE.md ├── scripts/ ← setup.sh and utility scripts ├── docker/ │ ├── CLAUDE.md │ ├── docker-compose.yml │ ├── docker-compose.override.yml │ ├── gw-build/ ← custom Ignition Dockerfile │ ├── gw-init/ ← gateway env vars (no secrets) │ ├── gw-commission/ ← commissioning.json │ ├── gw-config/ ← gateway runtime config (bind-mounted; runtime dirs gitignored) │ ├── gw-secret/ ← admin password file (gitignored) │ └── config/ │ ├── postgres/ ← init.sql │ └── traefik/ ← traefik.yml, dynamic/ ├── ignition/ │ ├── CLAUDE.md │ ├── ignition-api.md ← Gateway HTTP API reference │ └── project/ │ ├── com.inductiveautomation.perspective/ │ ├── com.inductiveautomation.webdev/ │ └── ignition/ │ ├── named-query/ │ ├── script-python/ │ └── global-props/ ├── testing/ │ ├── CLAUDE.md │ ├── scenarios/ │ ├── runner/ │ └── results/ ├── webdev/ │ ├── CLAUDE.md │ └── endpoints/ ├── tools/ │ └── (Python 3.10+ external utilities) └── docs/ ```