All checks were successful
PR checks / checks (pull_request) Successful in 9m56s
D55 bound the published port to 127.0.0.1, on the reasoning that TLS terminates
at a proxy on the same host and nothing else has business reaching the container.
That is right for the host this ends up on and wrong for every step before it: a
loopback binding cannot be opened from a browser on another machine, which is the
first thing an operator wants to do — look at the site on the VM's own address,
before DNS exists, before the proxy exists, from a desktop or a phone.
The port line is now "${SITE_BIND_ADDR:-0.0.0.0}:${SITE_HOST_PORT:-4321}:4321",
so http://<vm-ip>:4321 answers out of the box, the way a normal bridge publish
behaves. Which addresses it answers on is a variable rather than an edit:
SITE_BIND_ADDR narrows it to one interface, or back to loopback, without touching
a file that `docker compose pull` replaces. That also retires the "change the port
line yourself" instruction DEPLOY.md had to give a proxy running in another
container or on another machine.
What is given up, said plainly in DEPLOY.md §3.1: on a host with a public address
the default answers on port 4321 from the internet, plain HTTP beside the proxy's
443, with no proxy in the path to set X-Forwarded-For — so signups arriving that
way share one rate-limit bucket. There is no login and no secret behind it, so it
is untidy rather than dangerous, and both remedies are named (firewall the port,
or narrow the binding).
Verified by running it, not only by reading it: `docker compose config` accepts
both bindings and resolves host_ip 0.0.0.0 and 127.0.0.1 respectively; the stack
came up healthy, `docker compose port site 4321` reported 0.0.0.0:4321, and the
site answered 200 on both 127.0.0.1 and the machine's LAN address, still carrying
its own per-page CSP.
Recorded as D59, amending D55. Count of record fifty-nine.
Co-Authored-By: Claude <noreply@anthropic.com>
71 lines
3.0 KiB
Plaintext
71 lines
3.0 KiB
Plaintext
# runicgateway.com — production environment.
|
|
#
|
|
# Copy to `.env` beside docker-compose.yml on the host and fill in the two
|
|
# secrets. Everything else has a working default; this file exists so the
|
|
# defaults are visible rather than discovered.
|
|
#
|
|
# cp .env.example .env
|
|
#
|
|
# Nothing here is a credential for another service. The site talks to no API,
|
|
# sends no mail (D7) and has no database server — the only state it keeps is a
|
|
# SQLite file on the ./data mount.
|
|
|
|
# ---------------------------------------------------------------------------------------
|
|
# Deployment
|
|
# ---------------------------------------------------------------------------------------
|
|
|
|
# Which published build runs. `latest` follows main; pin `sha-<7>` for a
|
|
# reproducible deploy or to roll back — every merge publishes both tags.
|
|
IMAGE_TAG=latest
|
|
|
|
# Host port the container is published on.
|
|
SITE_HOST_PORT=4321
|
|
|
|
# Which of the host's addresses that port is published on. The default is every
|
|
# interface, so the site answers on the host's own address — http://<vm-ip>:4321
|
|
# — which is what a proxy in another container, another machine, or a browser
|
|
# elsewhere on the network needs.
|
|
#
|
|
# Narrow it if this host has a public address and you want only the proxy to
|
|
# reach the container: 127.0.0.1 for a proxy on this same host, or one interface
|
|
# address for the LAN but not a public NIC. Nothing else in the site changes.
|
|
SITE_BIND_ADDR=0.0.0.0
|
|
|
|
# ---------------------------------------------------------------------------------------
|
|
# The closed-beta signup (PLAN.md §8)
|
|
# ---------------------------------------------------------------------------------------
|
|
#
|
|
# THE TWO BELOW ARE THE ONLY VALUES THAT REALLY WANT SETTING. Both default to a
|
|
# random value generated per process, which is safe but forgetful: every restart
|
|
# invalidates every rate-limit window and every rendered form. That is the right
|
|
# default — a hard-coded salt shipped in a public repository would make every
|
|
# deployment's ip_hash values identical and therefore reversible by anyone who
|
|
# can read it — but it is not what you want on a host that restarts.
|
|
#
|
|
# Generate both once, keep them, and do not rotate them casually: changing the
|
|
# salt orphans the rate-limit history of everyone already counted.
|
|
#
|
|
# openssl rand -hex 32
|
|
|
|
# Salts the ip_hash column. The raw IP address is never stored — /privacy says
|
|
# so, and this is the mechanism that makes it true while still allowing a
|
|
# per-connection limit.
|
|
BETA_IP_SALT=
|
|
|
|
# Signs the hidden form token, so a script has to fetch the page before it can
|
|
# post. Rotating this only invalidates forms currently open in a browser.
|
|
BETA_FORM_KEY=
|
|
|
|
# Rows, across all time, above which the form closes and says so on the page.
|
|
BETA_TOTAL_CAP=500
|
|
|
|
# What one connection may do, in a rolling hour and a rolling day.
|
|
BETA_PER_HOUR=3
|
|
BETA_PER_DAY=24
|
|
|
|
# Seconds between the page rendering and the form posting. Below the minimum is
|
|
# treated as a script; above the maximum the form is stale and re-rendered.
|
|
# Twelve hours is the default maximum.
|
|
BETA_MIN_SECONDS=2
|
|
BETA_MAX_SECONDS=43200
|