Files
sharper.ly/.env.example
T
IllusionandClaude Opus 5 e27e31bfa5 Bind the production web server to a chosen port
react-router-serve only honours a port it is given: with PORT unset it calls
getAvailablePort(3000) and takes the first free port from 3000 upwards
(@react-router/serve cli.js). Nothing in the manual path set PORT, so a
busy 3000 silently moved the site to 3001, 3002 and so on, and a reverse
proxy configured against one upstream stopped matching. Docker never showed
this because apps/web/Dockerfile and docker-compose.yml both set PORT=3000.

The web start script now always passes a port - PORT if exported, else
WEB_PORT from .env, else 3000 - so the manual path is as deterministic as
the containerised one and .env's WEB_PORT finally reaches the process that
binds. HOST is deliberately left undefaulted: react-router-serve reads it
directly, unset means every interface (what the container needs) and
127.0.0.1 restricts the socket to a proxy on the same host.

Verified against the existing build: PORT=8123 listens on *:8123, and
WEB_PORT=8124 HOST=127.0.0.1 listens on 127.0.0.1:8124 and serves /en with
a 200 while the CMS is down.

.env.example documents both variables, and the deployment section of
README.md gains a systemd unit, since nothing sources .env there, and a
warning that WEB_PORT is the published host port under Docker but the
listen port in manual mode.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-20 11:33:02 +02:00

126 lines
5.5 KiB
Bash

# ============================================================================
# SHARP-ER - root environment template
# ----------------------------------------------------------------------------
# Copy once, then fill in: cp .env.example .env
# Generate the CMS secrets: ./scripts/generate-secrets.sh --write .env
#
# WHO READS THIS FILE
# * docker compose - every ${VARIABLE} in docker-compose.yml is read from
# the .env file sitting next to it (this one).
# * apps/web - the React Router server reads STRAPI_URL / STRAPI_TOKEN
# / SITE_URL from its process environment. In manual mode
# nothing loads this file for you, so export it first:
# set -a; . ./.env; set +a
# The defaults below are also the code defaults, so a
# plain `pnpm dev` works without exporting anything.
# * apps/cms - Strapi does NOT read this file. In manual mode it reads
# apps/cms/.env (cp apps/cms/.env.example apps/cms/.env).
# Under Docker the values below are injected into the
# container instead, so apps/cms/.env is not used there.
#
# Never commit .env. Never paste a real secret back into this template.
# ============================================================================
# ---------------------------------------------------------------------------
# DATABASE
# Manual mode: your local PostgreSQL 16 instance (scripts/setup-db.sh uses
# these values). Docker mode: the postgres service is created with them.
# ---------------------------------------------------------------------------
DATABASE_NAME=sharper_cms
DATABASE_USERNAME=postgres
DATABASE_PASSWORD=postgres
# Manual mode only - where the local server listens. Inside Docker the CMS
# always reaches the database as postgres:5432 on the compose network.
DATABASE_HOST=127.0.0.1
DATABASE_PORT=5432
# Docker mode only - host port the containerised database is published on.
# 5433 by default so it cannot collide with a PostgreSQL you already run on
# 5432. Connect with: psql -h 127.0.0.1 -p 5433 -U postgres sharper_cms
POSTGRES_PUBLISHED_PORT=5433
# ---------------------------------------------------------------------------
# CMS (Strapi 5) SECRETS
# Required. Every value must be a fresh random string - generate them with
# ./scripts/generate-secrets.sh --write .env
# or by hand with `openssl rand -base64 32`.
# APP_KEYS is a comma-separated list (at least two, so keys can be rotated).
# Rotating APP_KEYS logs everyone out; rotating ENCRYPTION_KEY makes values
# already encrypted in the database unreadable.
# ---------------------------------------------------------------------------
APP_KEYS=
API_TOKEN_SALT=
ADMIN_JWT_SECRET=
JWT_SECRET=
TRANSFER_TOKEN_SALT=
ENCRYPTION_KEY=
# ---------------------------------------------------------------------------
# PORTS
# Host ports the two applications are published on in Docker mode, and the
# ports they listen on in manual mode.
# ---------------------------------------------------------------------------
# React Router server (production build / Docker). `pnpm dev:web` uses 5173.
# Manual mode: `pnpm --filter web start` binds this port exactly - export it
# first (set -a; . ./.env; set +a) or pass it inline (PORT=8080 pnpm ...).
# Without it the server would pick the first free port from 3000 upwards,
# which a reverse proxy cannot be pointed at.
WEB_PORT=3000
# Manual mode only - interface the React Router server binds to. Unset means
# every interface. Set it to 127.0.0.1 when a reverse proxy on the same host
# is the only thing that should reach it. Leave it unset under Docker, where
# the container's own network namespace already isolates it.
# HOST=127.0.0.1
# Strapi API + admin panel.
CMS_PORT=1337
# Docker mode only - the absolute URL the admin panel and Strapi's generated
# links use. Change it together with CMS_PORT, and set it to the public origin
# in production (e.g. https://cms.sharper.ly).
CMS_PUBLIC_URL=http://localhost:1337
# ---------------------------------------------------------------------------
# WEB APP
# ---------------------------------------------------------------------------
# Public origin of the site. Used for canonical URLs, hreflang alternates and
# JSON-LD, so it must match how visitors actually reach the site.
# Leave it unset in local development and the request's own host is used.
SITE_URL=http://localhost:3000
# Where the server-side loaders fetch content from, MANUAL MODE.
STRAPI_URL=http://localhost:1337
# Where they fetch from inside Docker, where "cms" is the compose service name.
# NOTE: this origin is also prefixed onto uploaded media URLs, which the
# browser has to be able to resolve too - see "Images from the CMS" in the
# Docker section of README.md.
DOCKER_STRAPI_URL=http://cms:1337
# Optional Strapi API token. Only needed to read draft or otherwise protected
# content; published content is public. Create it in the admin panel under
# Settings -> API Tokens, then paste the value here (it is shown once).
STRAPI_TOKEN=
# ---------------------------------------------------------------------------
# CMS RUNTIME
# ---------------------------------------------------------------------------
# Browser origins allowed to call the API, comma separated. localhost:5173 and
# 127.0.0.1:5173 are always allowed outside production and need not be listed.
CORS_ORIGINS=http://localhost:3000
# Log verbosity: fatal | error | warn | info | http | debug | trace
LOG_LEVEL=info