11-service self-hosted stack with a Bash orchestrator

Dshamir converts the upstream prototype into a turnkey self-hosted deployment: a `docker-compose.yml` spanning 11 services, a `ailegal.sh` CLI that wraps Compose with health checks, dynamic port allocation, and database commands, and a round of first-run bug fixes to make the whole thing actually work on a fresh machine.

infrastructure

The Compose file brings up Postgres 16, Redis 7, MinIO, GoTrue (Supabase auth), PostgREST, pgAdmin, GlitchTip + its Celery worker, Nginx, and the backend and frontend containers. So this is not a Supabase-free fork - it runs gotrue and postgrest locally. The Prisma migration (a separate topic) only moves DB access off the Supabase SDK; auth and the REST interface remain. The backend image bundles LibreOffice via apk add libreoffice font-noto-cjk for document conversion. The frontend uses Next.js standalone output (output: "standalone") so the production image copies only what server.js needs.

ailegal.sh started as a straightforward Compose wrapper with build, up, down, health, db:migrate, and db:backup commands. It was substantially rewritten: 435 lines added to support dynamic port allocation that scans for conflicts via ss/netstat//dev/tcp, saves overrides to .ports, and exports the port vars so Compose picks them up. That last bit broke first - the initial compose file had hardcoded "5432:5432" literals everywhere, so even if the script picked alternate ports, Compose ignored them. A follow-up commit replaced all host-side port bindings with ${POSTGRES_PORT:-5432}:5432 etc.

Container-ownership detection also broke. The script tried to avoid false conflict warnings by checking if the process holding a port was one of its own containers, but it guessed container names (mike-postgres-1) rather than querying actual port bindings. The final fix switched to docker ps --filter "publish=$port" to ask Docker directly which container owns the port, and added -p mike to the Compose invocation so the project prefix is deterministic.

Two Dockerfile fixes were required for a working first run: the backend image was missing npx prisma generate --schema=prisma/schema.prisma before npx tsc (the generated Prisma client is a compile-time dependency), and the frontend build was missing NEXT_PUBLIC_* build args, which caused the static generation step to fail with an empty Supabase URL.

One caveat worth noting: minio, pgadmin, and glitchtip are pinned to :latest. That makes the stack reproducible day-to-day but not across months.

So what Worth a look if you want a working self-hosted reference for this stack. The port-conflict logic and the first-run Dockerfile fixes are the highest-value pieces to import. The `:latest` image tags are a reproducibility risk you would want to pin before relying on this in CI or for consistent team environments.

View this fork on GitHub →

Spotted something wrong? Or know the PR text has fresher detail than the writeup above?

Commits in this thread

13 commits from Dshamir/AI-Legal, oldest first. Source extracted verbatim from the harvested git log.

SHA Subject Author Date
867e043f [P1] chore: add editorconfig, dockerignore, update gitignore Dshamir 2026-05-23 ↗ GitHub
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
85f89ed5 [P1] feat: add backend Dockerfile with LibreOffice Dshamir 2026-05-23 ↗ GitHub
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
f496de45 [P1] chore: add consolidated environment template Dshamir 2026-05-23 ↗ GitHub
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
9aefc9d6 [P1] feat: add frontend Dockerfile with standalone output Dshamir 2026-05-23 ↗ GitHub
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cb2ce296 [P1] feat: add Nginx reverse proxy with upstream routing Dshamir 2026-05-23 ↗ GitHub
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
582919eb [P1] feat: add docker-compose with 11 services Dshamir 2026-05-23 ↗ GitHub
40c68333 [P1] feat: add ailegal.sh orchestration script Dshamir 2026-05-23 ↗ GitHub
Central CLI wrapping Docker Compose with build/rebuild/restart/health/status/
db/test/lint/bump/clean/nuke commands for the Mike platform's 11 services.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
d27cb584 [P1] feat: add docker-compose dev overrides example Dshamir 2026-05-23 ↗ GitHub
commit body
Provides hot-reload configuration for development. Targets the builder
stage of backend/frontend Dockerfiles and mounts source directories
read-only. Developers copy this to docker-compose.override.yml (which
is gitignored) for local customization.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4d3904b1 feat: revise ailegal.sh with dynamic port allocation, smoke tests, three-layer health Dshamir 2026-05-23 ↗ GitHub
714c7d73 feat: add ASCII banner to ailegal.sh help and startup Dshamir 2026-05-23 ↗ GitHub
33604cd8 fix: fix Dockerfiles and Quick Start for working first-run experience Dshamir 2026-05-23 ↗ GitHub
commit body
Backend Dockerfile was missing prisma generate before tsc, causing build
failure. Frontend Dockerfile needed NEXT_PUBLIC_* env vars for Next.js
static generation. README Quick Start now shows the real flow: build,
up, migrate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
734792f5 fix: wire dynamic port allocation through docker-compose.yml Dshamir 2026-05-23 ↗ GitHub
commit body
docker-compose.yml had hardcoded port literals (e.g. "5432:5432") so
ailegal.sh's dynamic port allocation was silently ignored - ports:auto
would reassign env vars but Compose always bound to defaults. Replace
all 11 host-side ports with ${VAR:-default}:container_port so exported
env vars actually take effect.

Also fix container ownership detection: ensure_dynamic_ports and
cmd_ports built container names from PORT_LABELS (e.g. mike-minio_api-1)
instead of the actual Compose service name (mike-minio-1), causing
multi-port services to always show CONFLICT. Add PORT_SERVICES array
mapping each port label to its Docker service name.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
0ef9d600 fix: detect container port ownership by actual port binding, not name Dshamir 2026-05-23 ↗ GitHub
commit body
Container ownership detection guessed names like mike-postgres-1, but
Docker Compose defaults the project name from the directory (ai-legal),
so all ports showed CONFLICT. Replace with docker ps --filter publish=N
which finds the actual container holding each port regardless of project
name prefix. Also pass -p mike to docker compose for consistent naming
going forward.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Capture this thread into my fork

Download a single Markdown prompt that tells Claude how to port every commit above into your working tree — adapting paths and structure to match your repo. Run it via claude -p < capture-thread-519.md from inside the repo you want the changes in.

⬇ Download capture-thread-519.md