First public release. - docs: document the audit log, user offboarding, CSV export and the "require a reason for destructive actions" setting across README, docs/ and CLAUDE/AGENTS.md; note retention for panel_audit - docs: fill the publishing placeholders (repo URL, security contact, issue-template links) and add a semantic-versioning policy - move agent/contributor context into CLAUDE/ (AGENTS.md + a README); root CLAUDE.md is now a thin @-import pointer - add a favicon (shield-lock, matching the login screen) to the login page and the app layout - CHANGELOG: promote [Unreleased] to [1.0.0]; keep the earlier internal build as [0.1.0] Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UfuvfwexztdS7LDpGuKsuK
4.1 KiB
Deployment (Docker)
The repo ships a small production stack in docker/:
docker/php/Dockerfile— PHP 8.4 + Apache,ldap/pdo_mysql/opcache, Composer, and an entrypoint that self-bootstraps on first boot.docker/php/entrypoint.sh— installsvendor/if missing, generatesAPP_KEY, fixesstorage/ownership, runsmigrate --force, warms caches, then starts Apache. Idempotent — runs on every start.docker/compose.yaml—app+mariadb, wired for an external Traefik proxy on thetraefik_publicnetwork.docker/compose.override.yaml.template— copy todocker/compose.override.yaml(git-ignored) to run without Traefik by publishing a host port.
With Traefik
cp .env.example .env
# APP_ENV=production, APP_DEBUG=false
# APP_URL=https://adconsole.example.com
# APP_DOMAIN=adconsole.example.com <- Traefik Host() rule
# DB_PASSWORD / DB_ROOT_PASSWORD <- set real values
# LDAP_* / AD_* <- your domain
docker compose -f docker/compose.yaml up -d --build
docker compose -f docker/compose.yaml logs -f app # watch the bootstrap
Enable a certificate resolver by uncommenting the certresolver label in
docker/compose.yaml and setting its name to match your Traefik config.
The .env file is bind-mounted, not baked in: edit it and
docker compose restart app — no rebuild needed. (This is deliberate; see the
comment in compose.yaml.)
Without Traefik
cp docker/compose.override.yaml.template docker/compose.override.yaml
# APP_URL=http://localhost:8080 in .env
docker compose -f docker/compose.yaml -f docker/compose.override.yaml up -d --build
App on http://localhost:8080, MariaDB on localhost:3306. Put a TLS-terminating
proxy (nginx, Caddy, your load balancer) in front for anything but a lab.
Data & backups
| What | Where | Back up? |
|---|---|---|
Database (panel_settings + panel_attributes config, panel_audit change log, sessions, cache) |
db_data volume |
Yes — panel_settings / panel_attributes are your config and panel_audit is your only record of who changed what. mysqldump adconsole. |
storage/ (logs, framework cache, sessions if file-driver) |
app_storage volume |
Optional (logs only really). |
.env |
bind-mounted from the repo | Yes, securely — it holds the service-account password. |
Restore config elsewhere: mysqldump/import the panel_settings and
panel_attributes tables, or re-enter it in /settings.
Audit-log retention. panel_audit grows with panel activity and nothing
prunes it automatically. If you need a retention window, trim it with a scheduled
job of your own, e.g.
DELETE FROM panel_audit WHERE created_at < NOW() - INTERVAL 365 DAY;, and
back it up (or ship it to a SIEM) before trimming if the entries have
compliance value.
Upgrades
git pull
docker compose -f docker/compose.yaml up -d --build
The entrypoint runs migrate --force and optimize on the new container. See
upgrading.md for version-specific notes.
Hardening checklist
APP_DEBUG=false,APP_ENV=production- HTTPS only; HSTS at the proxy
LDAP_SSL=true(orLDAP_TLS=true);LDAP_TLS_VERIFY=trueonce the CA is trusted by the app host- Service account delegated only the rights you use (active-directory.md)
AD_LOGIN_GROUPis a small, audited group- Panel reachable only from admin networks / VPN (proxy ACL or firewall)
- DB not exposed to the internet; strong
DB_PASSWORD/DB_ROOT_PASSWORD .envfile permissions600, owned by the deploy user- Log shipping / retention configured; review
LDAP_LOGGINGvolume - Backups of the database tested
Scaling notes
- Multiple app containers: move
SESSION_DRIVERand the app caches off local disk (database/redis) — note the OU-tree/dashboard/org-chart caches are hard-coded to thefilestore, so with >1 node each node keeps its own (they expire in minutes; acceptable, or add a shared store and changeCache::store('file')call sites). - One
mariadbis plenty; the workload is tiny.