Skip to content

feat: add Docker Compose setup for local development#1

Closed
aberoham wants to merge 15 commits into
masterfrom
feat/docker-compose
Closed

feat: add Docker Compose setup for local development#1
aberoham wants to merge 15 commits into
masterfrom
feat/docker-compose

Conversation

@aberoham

@aberoham aberoham commented Apr 5, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Replace the old dist/docker/debian/web/ layout with a flattened dist/docker/ structure
  • Dockerfile based on Debian bookworm with Apache/mod_perl
  • docker-compose.yml with MariaDB 11 + web service, health checks, volume persistence
  • Entrypoint with auto DB init, runtime config generation for nictoolserver.conf and test.cfg
  • Test user setup script

Usage

cd dist/docker
docker compose up -d
# NicTool available at http://localhost:8080
# Login: root / nictool

Test plan

  • docker compose build succeeds
  • docker compose up -d starts both containers healthy
  • Login page loads at http://localhost:8080
  • Login with root/nictool succeeds
  • DB persists across container restarts (volume)

🤖 Generated with Claude Code

the .test/, dist/docker/debian/, and .dist configs were three copies of
roughly the same thing with different hardcoded passwords. now there's
one set of env-var-aware .dist configs and a shared setup script that
Docker and CI both call.

what changed:
- nictoolserver.conf.dist and nictoolclient.conf.dist read credentials,
  hosts, ports, protocol from env vars (safe defaults for non-sensitive
  stuff, die() for passwords so you know what to set)
- new dist/setup/ with install-nictool.sh, apache.conf.in template,
  tls-setup.sh, and setup-test-env.pl for generating test users
- dist/docker/ flattened -- new Dockerfile with layer caching so code
  changes rebuild in ~2s, docker-compose.yml, entrypoint, and a
  generate-env.sh that creates random passwords
- healthcheck on the web container so --wait actually waits for Apache
- ci.yml generates random creds per run instead of sed-ing hardcoded
  passwords into config copies
- new docker.yml workflow that builds the image and runs server, client,
  and all 120 E2E tests against it
- deleted 7 redundant .test/ files and the old dist/docker/debian/ tree
- `git grep -i lootcin` returns zero matches now
- test.cfg files gitignored, generated at runtime by setup-test-env.pl
- perltidy on all modified perl files

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@aberoham aberoham force-pushed the feat/docker-compose branch from fda24ac to 9805233 Compare April 5, 2026 22:29
aberoham and others added 11 commits April 5, 2026 23:36
Pre-build the image with docker/build-push-action using GitHub Actions
cache (type=gha, mode=max) so the expensive apt/cpanm layers persist
across runs. Code-only changes should skip straight to the final COPY
layer.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
the GitHub Actions MySQL runner uses caching_sha2_password which
requires SSL. the old .test/create_tables.pl hardcoded mysql_ssl => 1,
but we need it parameterized so it works everywhere.

DB_SSL=1 enables mysql_ssl=1 in the DSN and DBI connect options for
nictoolserver.conf.dist, create_tables.pl, and setup-test-env.pl.
unset or empty means no SSL (Docker's MariaDB doesn't need it).

CI sets DB_SSL=1, Docker doesn't.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
brought back .test/apache-matt.conf with:
- PerlPassEnv directives so the env-var-aware .dist configs work
- Apache 2.4 Require all granted (was 2.2 Order/Allow)
- quoted paths for spaces safety
- added Directory block on the server vhost
- header comment showing how to use it (or just run install-nictool.sh)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
create_tables.pl grants to nictool@$DB_HOSTNAME. with 127.0.0.1,
MySQL's reverse lookup sees the connection as 'localhost' which doesn't
match the grant for '127.0.0.1'. the old .test/create_tables.pl used
localhost and granted to localhost/127.0.0.1/::1 — matching the runner's
MySQL behavior.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
MySQL 8.0 removed GRANT ... IDENTIFIED BY for user creation. the old
syntax silently failed to create the nictool user, so setup-test-env.pl
couldn't connect.

now uses CREATE USER (with mysql_native_password, falling back to
default plugin) followed by a separate GRANT — same pattern the old
.test/create_tables.pl used.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
mod_perl requires mpm_prefork — Ubuntu defaults to mpm_event which
causes 500 errors on mod_perl handler requests. the Dockerfile already
did this but the shared install script didn't.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CI: sudo cpanm leaves root-owned build artifacts that Apache (www-data)
can't read, causing "Permission denied" on URI/_generic.pm. Add chmod
before Apache restart.

Docker: $dbh->do() doesn't throw exceptions by default (no RaiseError),
so the eval { do(); 1 } pattern always returned 1. The mysql_native_password
syntax fails on MariaDB but the fallback never ran. Use return value check
instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CI: The 500 error was AppArmor, not Unix permissions. Ubuntu 22.04's
Apache profile blocks reads from /home/runner/work/ — EACCES even for
nonexistent files, which is why URI/_generic.pm showed "Permission
denied" despite not existing. Disable the profile in CI.

Docker: cache npm packages (setup-node) and Playwright browsers
(actions/cache) so E2E dependency installs are fast on repeat runs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Docker workflow is now three jobs: build (populates GHA layer cache),
docker-tests (server + client), and e2e-tests (Playwright). The test
jobs run in parallel after the build, each loading the image from cache.
Node bumped from EOL 18 to LTS 22.

CI: the 500 error was Ubuntu's AppArmor profile for Apache blocking
reads from /home/runner/work/ — returning EACCES even for nonexistent
files (which is why URI/_generic.pm showed "Permission denied" despite
not existing in client/lib/). Disable the profile in CI.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CI: Instead of disabling AppArmor, symlink the workspace to
/var/www/nictool and use that as NT_DIR. Apache's AppArmor profile
allows /var/www/ reads.

Docker E2E: add a curl-based login verification step before Playwright
to diagnose why login returns no session cookie. Also fix password
extraction to handle base64 = chars (sed instead of cut -d=).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@aberoham aberoham force-pushed the feat/docker-compose branch from c4305ab to d0070a0 Compare April 5, 2026 23:53
aberoham and others added 3 commits April 6, 2026 00:57
Symlinks don't work — AppArmor resolves to the real path. Instead,
add a local rule allowing Apache to read $GITHUB_WORKSPACE/** and
reload the profile. This is the standard AppArmor mechanism for
site-specific additions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Not AppArmor (no profile exists on the runner). Ubuntu 21.04+ defaults
home directories to 750, so www-data can't traverse /home/runner/.
Add o+x to each parent dir in the workspace path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@aberoham aberoham closed this Apr 6, 2026
@aberoham aberoham deleted the feat/docker-compose branch April 6, 2026 06:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant