Secure Deployment Boundary
This is the normative secure-deployment procedure. The secure deployment guide is a short operator entry point and the security architecture records the product posture; neither changes the requirements in this runbook.
Supported exposure model
Riviamigo is not approved for direct Internet exposure. The standard production
Compose stack publishes its web origin on port 8080 using normal Docker host
publication bound to 127.0.0.1 by default; RIVIAMIGO_HOST_BIND_ADDRESS
requires an explicit override for network sharing. It runs the app as UID/GID 1001, with a read-only root filesystem, all Linux
capabilities dropped, no-new-privileges, and a bounded /tmp tmpfs. Database
initialization and migrations run inside that same unprivileged app container;
the production stack has no root init service. Do not weaken these defaults to
make an origin public.
Place an authenticated tunnel or identity-aware reverse proxy and host firewall
rule in front of that origin. A tunnel that only publishes the port
without an access policy is not sufficient. A non-loopback bind requires both
RIVIAMIGO_BIND_ADDRESS and the explicit
ALLOW_PUBLIC_ORIGIN_BIND=true opt-in; it remains unsupported as a direct
Internet exposure pattern.
The outer gateway must terminate public HTTPS, require an identity policy, and
forward normal HTTP and WebSocket traffic to http://localhost:8080. Riviamigo
login remains enabled behind that gateway. Cloudflare Tunnel with Access and
Authentik in front of Caddy, Nginx, or Traefik are supported deployment shapes;
the gateway itself is operated and patched by the self-hoster.
Required production configuration
- Riviamigo defaults to production mode; use
RIVIAMIGO_ENV=developmentonly for local development. - Configure a 32-byte-or-longer production first-owner proof through exactly
one of
RIVIAMIGO_SETUP_TOKENorRIVIAMIGO_SETUP_TOKEN_FILE. The setup endpoint reports availability but never reveals which source is used. Before a user exists, registration fails closed without a valid proof; after the first owner claims the instance, remove or rotate the bootstrap proof. - Let Riviamigo generate and persist its application keys in PostgreSQL, or
supply
JWT_SECRET,JWT_PUBLIC_KEY, andAGE_ENCRYPTION_KEYtogether from a secret manager. Partial overrides fail startup. Database-persisted keys are an explicitly accepted P2 shared-fate risk; preserve PostgreSQL backups and, for externally managed keys, document and test the secret-manager recovery path. - Set
ALLOWED_ORIGINSto the exact public HTTPS origin, with no path. - Set strong
POSTGRES_PASSWORDandREDIS_PASSWORDvalues. Standard Compose safely constructs its internal URLs; customDATABASE_URLvalues must be valid URLs. - Keep
COOKIE_INSECUREabsent. It is local-development-only. - Do not publish API port 3001, PostgreSQL port 5432, Redis port 6379, or the origin port 8080 directly to the Internet.
Exceptional trusted-LAN HTTP access
Do not use this procedure for public, guest, or untrusted Wi-Fi networks. If HTTPS cannot be provided on an isolated trusted LAN, set all of these in the Compose environment file:
RIVIAMIGO_HOST_BIND_ADDRESS=192.168.1.20
RIVIAMIGO_BIND_ADDRESS=0.0.0.0
ALLOW_PUBLIC_ORIGIN_BIND=true
ALLOWED_ORIGINS=http://192.168.1.20:8080
ALLOW_INSECURE_LAN_HTTP_AUTH=true
The final variable is a strict boolean and defaults to false in the standard
Compose file. Startup accepts only private, loopback, or link-local literal IP
HTTP origins; it rejects hostnames, public IPs, paths, credentials, and mixed
HTTP/HTTPS origin lists. This is an intentional reduction in transport
protection: refresh cookies remain HttpOnly, SameSite=Lax, rotating, and
revocable, but they no longer carry the Secure attribute. Restrict the port
to the trusted LAN with host firewall rules, disclose the interception risk to
users, and restore HTTPS as soon as possible.
Gateway requirements
- Enforce authentication before forwarding any request, including
/v1/*and WebSocket upgrades. - Preserve
Hostand WebSocket upgrade headers. Do not logAuthorizationorSec-WebSocket-Protocolheaders. - Preserve live-status control frames and configure the gateway's websocket idle/read timeout above 90 seconds. Riviamigo sends a keepalive every 30 seconds and the browser reconnects when it misses the 90-second liveness window.
- Own public TLS, certificate renewal, Internet-facing rate limits, and any trusted-client-IP policy. The internal Riviamigo origin intentionally does not trust arbitrary forwarded client IP headers.
- Restrict direct host access to port 8080 with host firewall rules.
Trusted gateway client addresses
By default nginx replaces forwarded-IP headers with the socket peer address.
If the gateway supplies sanitized client addresses, mount an operator-owned
configuration under /etc/nginx/trusted-proxy/. Trust only the gateway's exact
CIDRs, never every network:
set_real_ip_from 192.168.1.10/32;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
Mount that file read-only into the unified app container, for example at
/etc/nginx/trusted-proxy/gateway.conf. Verify spoofed forwarded chains cannot
change the address nginx passes to the loopback API. The API accepts one IP
from this trusted local nginx, never an arbitrary comma-separated chain.
Gateway-on-Docker-network deployments should remove the app host ports
publication and retain Traefik's service port 8080 on its private network.
Non-root Nginx diagnostics
Nginx writes errors to stderr from its initial startup through normal service
operation. The supervisor uses -e stderr before configuration is loaded, and
the main-context error_log stderr warn; retains that destination afterward.
This works with the read-only filesystem and UID1001 container policy without
requiring write access to Nginx's package-default log directory.
Use nginx -e stderr -t when checking configuration under the same constraints.
See the Nginx command-line options and
error_log directive.
Verification
- Run
docker compose --env-file .env -f compose/docker-compose.yml configand confirm the unified app publishes the intentionally selectedRIVIAMIGO_HOST_BIND_ADDRESSandRIVIAMIGO_ORIGIN_PORTvalues, with no database or Redis port. - Start the stack and check
curl http://localhost:8080/healthlocally. - Confirm external access is denied by the gateway before reaching Riviamigo, then authenticate through the gateway and sign in to Riviamigo.
- Confirm
docker compose --env-file .env -f compose/docker-compose.yml psshows no host mapping for the internal API listener, TimescaleDB, or Redis. - Run
pnpm docs:checkand the security test suite before upgrading a shared instance. - From the public address, leave a signed-in dashboard open beyond the
gateway idle window, background and refocus the tab, and confirm the status
transitions through
Reconnecting...toOnlinewithout a page reload. - Confirm
docker compose ... configretainsuser: "1001:1001",read_only: true,cap_drop: [ALL], andno-new-privileges:truefor the long-livedriviamigoservice. These are deployment controls, not optional tuning.
Limits of this guidance
This boundary reduces exposure; it is not a security certification or a substitute for gateway patching, host hardening, backups, monitoring, and an independent penetration test when the deployment risk warrants one.