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.
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.
Spotted something wrong? Or know the PR text has fresher detail than the writeup above?