diff --git a/.dockerignore b/.dockerignore index 14dc2d3..8c8fb9a 100644 --- a/.dockerignore +++ b/.dockerignore @@ -14,10 +14,12 @@ apps/cms/.strapi apps/cms/.tmp apps/cms/.cache -# Secrets. The containers receive their environment from docker compose. -.env -.env.* -!.env.example +# Secrets. The containers receive their environment from docker compose, so no +# .env belongs in an image - including the per-application ones (apps/web/.env, +# apps/cms/.env), which COPY apps/web / COPY apps/cms would otherwise pick up. +**/.env +**/.env.* +!**/.env.example # Licensed fonts are mounted or copied in at deploy time, never baked into a # published image. See the FONTS section of README.md. diff --git a/.env.example b/.env.example index 2841ca1..3b1f2ce 100644 --- a/.env.example +++ b/.env.example @@ -7,12 +7,13 @@ # 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/web - NOT this file. In manual mode the web app reads +# apps/web/.env (cp apps/web/.env.example apps/web/.env), +# which is where PORT, HOST, SITE_URL, STRAPI_URL and +# STRAPI_TOKEN belong. Under Docker the values below are +# injected into the container instead. The defaults are +# also the code defaults, so a plain `pnpm dev` works +# with no .env at all. # * 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 @@ -63,23 +64,18 @@ ENCRYPTION_KEY= # --------------------------------------------------------------------------- # PORTS -# Host ports the two applications are published on in Docker mode, and the -# ports they listen on in manual mode. +# Host ports the two applications are published on in Docker mode. The ports +# they LISTEN on in manual mode live in each application's own .env +# (apps/web/.env, apps/cms/.env), because both servers read a bare PORT. # --------------------------------------------------------------------------- -# 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. +# Docker mode - host port the web container is published on; inside the +# container it always listens on 3000, so WEB_PORT=8080 means 8080:3000. +# Manual mode has its own PORT in apps/web/.env - never put a bare PORT in +# THIS file: both servers would read it and Strapi would land on the website's +# port. `pnpm dev:web` uses 5173 either way. 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 @@ -90,7 +86,7 @@ CMS_PUBLIC_URL=http://localhost:1337 # --------------------------------------------------------------------------- -# WEB APP +# WEB APP - Docker mode. In manual mode these live in apps/web/.env. # --------------------------------------------------------------------------- # Public origin of the site. Used for canonical URLs, hreflang alternates and @@ -98,9 +94,6 @@ CMS_PUBLIC_URL=http://localhost:1337 # 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 diff --git a/README.md b/README.md index c9b9365..3d821eb 100644 --- a/README.md +++ b/README.md @@ -195,12 +195,17 @@ sudo -u postgres psql -c "CREATE ROLE sharper WITH LOGIN PASSWORD 'a-strong-pass ### 4. Environment files -Two files, with different jobs: +Three files, with different jobs: | File | Read by | Required in manual mode | | --- | --- | --- | | `apps/cms/.env` | Strapi (it loads its own `.env`) | **yes** | -| `.env` (root) | `docker compose`, and optionally the web server | no — the code defaults already point at `localhost` | +| `apps/web/.env` | `pnpm --filter web start` (it loads its own `.env`) | no — but it is where `PORT` goes | +| `.env` (root) | `docker compose` only | no | + +Each application reads its **own** `.env`, and the root one belongs to `docker compose`. +That split is not cosmetic: `PORT` and `HOST` mean something different to each server, and a +single shared value would put Strapi on the website's port. ```bash # CMS: copy the fully documented template, then fill in the six secrets @@ -222,17 +227,19 @@ overwrites a value that is already set unless you pass `--force`, so it is safe Without `--write` it just prints a block you can paste. By hand: `openssl rand -base64 32`, once per key (`APP_KEYS` takes at least two, comma separated). -The web application reads `STRAPI_URL`, `STRAPI_TOKEN` and `SITE_URL` from its **process -environment**. Nothing loads the root `.env` for you in manual mode, and you do not need it: -`STRAPI_URL` already defaults to `http://localhost:1337`, and with `SITE_URL` unset the -canonical URL is derived from the incoming request. To override them anyway: +The web application needs nothing to start: `STRAPI_URL` already defaults to +`http://localhost:1337`, and with `SITE_URL` unset the canonical URL is derived from the +incoming request. Copy its template when you want to override those, or to pin the +production port: ```bash -cp .env.example .env -set -a; . ./.env; set +a # export it into the current shell -pnpm dev +cp apps/web/.env.example apps/web/.env # PORT, HOST, SITE_URL, STRAPI_URL, STRAPI_TOKEN ``` +`pnpm --filter web start` loads that file. `pnpm dev:web` does not — the dev server is Vite +on 5173 — so export anything you need for development, or put it in the file for the +production server and pass it inline while developing. + ### 5. Run both applications ```bash @@ -469,7 +476,7 @@ docker run --rm -v sharper_cms-uploads:/data -v "$PWD":/backup alpine \ | --- | --- | | `dev` | `react-router dev` — SSR dev server with HMR | | `build` | `react-router build` → `build/client` (static assets) + `build/server` (SSR bundle) | -| `start` | the production server — `react-router-serve ./build/server/index.js` on `PORT`, falling back to `WEB_PORT`, then 3000 | +| `start` | the production server — `react-router-serve ./build/server/index.js`, started with `node --env-file-if-exists=.env` so `apps/web/.env` can set `PORT` | | `typecheck` | regenerates `+types/*` route types, then `tsc` | **`apps/cms`** — run with `pnpm --filter cms