Skip to content

Battle arena and league improvements - #25

Merged
TheCodeHeist-Coder merged 3 commits into
mainfrom
battle-arena-and-league-improvements
Sep 13, 2026
Merged

TheCodeHeist-Coder merged 3 commits into
mainfrom
battle-arena-and-league-improvements

Conversation

@TheCodeHeist-Coder

Copy link
Copy Markdown
Owner

No description provided.

TheCodeHeist-Coder and others added 3 commits September 13, 2026 13:12
Matches the shape already in use on the other project: flat services,
4-space indent, container_name on each, one bridge network, env_file
instead of per-key substitution, and the host-database note where the
db service would otherwise be. 303 lines down to 172.

Gone with it: the x-app-env / x-host-access / x-logging anchors, and the
long prose header. Reading a service no longer means resolving three
anchors defined a hundred lines up.

NGINX

Added nginx-proxy-manager, as in Rexial. It terminates TLS and fronts
web, http-api and ws-server, so those three no longer publish loopback
ports of their own — the proxy reaches them over the compose network.
81 is the admin UI; firewall it or bind it to loopback once configured.

DEPLOYS NO LONGER TOUCH THE DATABASE

The rollout step used to run db-init on every push to main, and db-init
re-runs a seed that DELETES every problem and test case before inserting
three samples. On a disposable dev database that is harmless; against
the host Postgres on the VPS it destroys player- and admin-authored
problems on every deploy.

Nothing depends on db-init now and the deploy does not run it. Apply a
schema change deliberately:

  docker compose -f docker-compose.prod.yml run --rm db-init

Forget it and the apps run against the old schema — a visible failure,
which is better than a deploy that can quietly rewrite data.

WHY db-init SURVIVES AT ALL

Rexial runs migrations as `command: pnpm db:migrate && pnpm start` on
the service itself. That cannot work here: the prod images install with
--prod --ignore-scripts and copy only dist/, so the runtime stage has no
Prisma CLI and no prisma/ directory. Verified by building http-api's
Dockerfile.prod and looking inside. db-init is the only image carrying
the CLI, so it stays as the one-shot client.

NEXT_PUBLIC_* KEEP DEFAULTS

They are build args, and build args do not come from env_file. `:?` was
tried first and broke every command on the server — pull, ps and up -d
all interpolate the whole file, and the VPS never sets them because it
only pulls. Defaults keep those working; CI exports the real values when
it builds, and the placeholders never reach an image.

Verified with `docker compose config` both ways: no build args in the
shell (the VPS path) and with them set (the CI path).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
nginx, TLS and certbot are managed on the server by hand, so a proxy
container in this file would be a second thing claiming :80 and :443 and
a second place certificates could live.

web, http-api and ws-server publish to 127.0.0.1 again — 3001, 4001 and
4002 — which is how the host nginx reaches them. Loopback, not 0.0.0.0:
binding the wildcard would serve plaintext HTTP on the public interface
and let anyone skip TLS entirely. Verified with `compose config` that all
three resolve to 127.0.0.1 and that nothing else publishes a port.

The nginx_data and letsencrypt volumes go with it; certbot keeps its
certificates under /etc/letsencrypt on the host.

DEPLOYMENT.md rewritten for this shape: firewall, host Postgres and Redis
(bridge binding and pg_hba), .env.prod, both nginx server blocks, certbot,
CD secrets, and troubleshooting. It stays gitignored, as asked earlier.

The nginx config in it is not illustrative — both server blocks were
extracted from the doc and checked with `nginx -t` in a container. The
/ws block sits above `location /` deliberately, since a catch-all above
it swallows the upgrade, and it carries a 3600s read timeout because a
battle can idle between moves and the 60s default would drop it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
admin was the only app without a Dockerfile, so it was built by CI, type
checked, linted — and then never shipped. Nothing deployed it and nothing
proxied to it.

Adapted from apps/web/Dockerfile.prod, which is the same shape: Next.js
with output: "standalone" and outputFileTracingRoot at the monorepo root,
so the traced tree keeps the workspace layout and server.js lands at
apps/admin/server.js. Differences are the turbo filter, port 3002, and
one build arg rather than two — admin calls the REST API and holds no
WebSocket, so NEXT_PUBLIC_WS_URL would be dead weight in its bundle.

Verified by building and running it, not by reading the template: the
image is 335MB, serves 200 on /, reaches `healthy`, and returns the
public/ asset. CI's typecheck and lint still pass.

Wired into docker-compose.prod.yml on 127.0.0.1:3002 like the others —
host nginx reaches it, the internet does not — and into the deploy
matrix, which already passes NEXT_PUBLIC_* to every image, so no build
arg plumbing was needed. Comments that counted five images now say six.

DEPLOYMENT.md gains an admin server block on its own subdomain. It ships
with `allow`/`deny` restricting it by source address, with basic auth as
the alternative: the app has its own login, but an admin panel open to
the internet is a login form to brute-force. All three server blocks in
that doc were extracted and checked with `nginx -t`.

Still missing: admin has no Dockerfile.dev and is absent from the local
compose stack, so `pnpm dev` remains the way to run it locally.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@TheCodeHeist-Coder
TheCodeHeist-Coder merged commit 39ddc8c into main Sep 13, 2026
6 checks passed
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