# Installation Guide For the quick-start commands, see [Installation](https://github.com/j5guy/fondwaypoints#installation) in the README. This page covers the full guided-setup walkthrough and doing it by hand instead. For a step-by-step account of exactly what the setup script installs, writes to disk, and sends over the network — useful if you want to know before running it, or something failed partway through — see [What the Setup Script Does](What-the-Setup-Script-Does.md). Already installed and want to update instead? See [Updating](Updating.md). **The [guided setup](#guided-setup-in-full) below is the quickest and easiest way to get running.** Prefer to skip the repo checkout entirely and run just the published Docker image yourself instead? Skip ahead to [Just the image, no repo checkout](#just-the-image-no-repo-checkout). ## Guided setup, in full One command installs whatever's missing (git, curl, Node), clones the repo into a scratch directory, and walks you through `.env` in a browser form (with a "generate" button for the two secrets) — no `git clone`/`mkdir` needed first: ```bash curl -fsSL https://raw.githubusercontent.com/j5guy/fondwaypoints/master/install.sh | bash ``` Already have a checkout, or want to review the script first? `git clone` it and run `./install.sh` (`./setup.sh` is kept as an alias) from inside it instead — this operates in place, with no scratch clone or `--dir` involved. > **Testing scope:** this install path has only actually been run on Rocky Linux 9 and Ubuntu 24.04/26 > (x86_64) — Ubuntu tested as a VM and on bare metal, *not* as an LXC container (see > [Ubuntu LXC is not recommended](#docker-deployment-in-full) below). It handles a lot of environment variation > automatically and won't be perfect everywhere — please [report anything > that breaks](https://github.com/j5guy/fondwaypoints/issues) with your OS/version and what happened. See > [What the Setup Script Does](What-the-Setup-Script-Does.md) for exactly what it does, and [Manual / > non-Docker deployment](#manual--non-docker-deployment-in-full) or the manual `docker compose` > commands below as a fallback. > > **ARM / Raspberry Pi:** untested, but should mostly work — Node and Docker Engine both officially > support Debian/Ubuntu on arm64/armhf. The guided setup always builds the image locally — see > [Why build locally instead of pulling an image?](https://github.com/j5guy/fondwaypoints#why-build-locally-instead-of-pulling-an-image) > in the README (this project doesn't publish a pre-built image itself — see > [CI/CD and Releases](CI-CD-and-Releases.md)) — so there's no architecture mismatch to worry about > here. The image build compiles `bcrypt` from source as a fallback if no prebuilt binary exists for > your platform (see `Dockerfile`) — slower, but should still succeed. > > **MongoDB on ARM:** MongoDB 5.0+ (and 4.4.19+) require ARMv8.2-A, which pre-Pi-5 hardware > (Cortex-A72/A53 — Pi 4 and earlier) doesn't implement, and fails with a `WARNING: MongoDB requires > ARMv8.2-A or higher` message (and likely a crash once it actually hits an unsupported instruction). If > you pick **Internal MongoDB**, the wizard checks for this (the `atomics` CPU feature in > `/proc/cpuinfo`) and shows a plain notice next to the MongoDB choice saying which image it'll install > and why — `mongo:7` normally, or `mongo:4.4.18` (the last release before that requirement was added) > automatically instead if this CPU can't run it. There's nothing to fill in either way. This is a > fallback, not a recommendation: 4.4 has been EOL since February 2024 (no further security patches). > **External MongoDB**, pointed at hardware without this limitation, avoids the tradeoff entirely and > is preferable where that's an option. To override the auto-detected choice, edit `MONGO_IMAGE` > directly in `.env` after the wizard writes it. The script installs Node if missing (`apt`/`dnf` + NodeSource on Linux). Docker is deliberately *not* installed at this stage — only once you actually pick Docker deployment in the form below does the wizard attempt to install it (Docker Engine on Linux via `get.docker.com`, best-effort and non-fatal). You may need to log out/in for new group membership (`docker` without `sudo`) to take effect. If auto-install fails or isn't available, the wizard prints a manual install link and exits — fix the issue and re-run the script. Early in the form (only when installed via the curl command above, into a scratch clone — not when run from an existing checkout, which has nowhere else to go) it asks **what should be left on disk once this finishes**: - **Docker only, minimal footprint** (recommended) — builds the image, then deletes the source, leaving a small self-contained `docker-compose.yml` (no build context — its `app.image` is a stably-tagged local build, `fondwaypoints-app:local`), `.env`, `.deploy-state.json`, and `update.sh` at the install directory. Forces Docker deploy mode (Local Node.js process needs the source kept on disk to run `node server.js` directly) — shown grayed out below. Docker images persist independently of the checkout, so building still works fine even though the checkout gets deleted afterward. See `scripts/lib/footprint.js`. - **Full checkout** — keeps the source at the install directory (`/opt/fondwaypoints` by default; `--dir`/`FONDWAYPOINTS_INSTALL_DIR` to change it), so Local Node.js process (below) stays available too. Partway through the form it asks **how to run the app**: - **Docker** (recommended) — runs a bundled nginx alongside the app that terminates TLS itself, unless "Existing nginx" below is used instead. See [Docker deployment](#docker-deployment-in-full) below for what that needs from you. Always builds the image locally (`docker compose build`), matching the checkout the wizard just cloned — see [Why build locally instead of pulling an image?](https://github.com/j5guy/fondwaypoints#why-build-locally-instead-of-pulling-an-image) in the README; pulling a published image is still possible manually, just not from the guided setup. It also asks: - **MongoDB**: **Internal** (recommended) brings up a bundled `mongo` container and points `mongoHost` at it automatically; **External** skips that container — fill in `mongoHost`/`mongoUser`/`mongoPass` for your own server further down the form. - **Local Node.js process** — runs `node server.js` directly on this machine, registered as a `systemd` background service so it survives reboots. Bundles nothing — you must point `mongoHost` at an **external MongoDB server** (the form shows a warning about this once you pick this option), and either enable "Existing nginx" below or put your own TLS-terminating reverse proxy in front of it yourself (see [Manual / non-Docker deployment](#manual--non-docker-deployment) below). This shells out to `sudo` to write the unit file and enable/restart the service — expect a sudo password prompt. > **If you choose "Local Node.js process" and don't enable "Existing nginx" below, you must run a > reverse proxy — like nginx — yourself to handle TLS.** This mode does not terminate TLS for you the > way Docker mode does, and TLS is mandatory (see above); without a reverse proxy in front of it, the > app won't be reachable securely at all. Next, an **Existing nginx** section always appears, for either deploy mode, with clear feedback either way about what will actually terminate TLS: - If nginx is already installed on this host, an INFO note says so, with a checkbox to add a site for it automatically — reverse-proxying `WEB_FQDN` over TLS to wherever the app is actually reachable (the local service's own fixed port, or the app container's own fixed port exposed directly in place of the bundled Docker nginx sidecar — see [Docker deployment](#docker-deployment-in-full) below). A second field, **Port for direct LAN-IP HTTPS access** (`HOST_NGINX_IP_PORT`, defaults to `8443`, live- checked for availability the same way — see below), appears alongside the checkbox: this host's nginx may front other sites too, so the `WEB_FQDN` site deliberately doesn't also answer to the bare IP on `443` (that could steal the IP from those other sites) — instead it gets its own dedicated listener on this port. The site is (re)written from scratch into whatever layout this host's nginx uses (`sites-available`/`sites-enabled`, or `conf.d`) — any previous version is removed first — then `nginx -t`, then reload, all via `sudo`. This makes the app reachable at `https://WEB_FQDN/` *and* `https://:/`, both actually through nginx. Leaving the checkbox unchecked in Docker mode shows a green PASS note confirming the bundled Docker nginx container will be deployed instead. - If nginx isn't installed, a green PASS note says so in Docker mode ("the bundled Docker nginx container will be deployed instead"), or a yellow INFO note in local mode pointing back at the reverse-proxy warning above. In Docker mode, a **Port for HTTPS access** field (`NGINX_HTTPS_PORT`, defaults to `5560`, live-checked the same way) lets you pick what port the bundled nginx container itself listens on — the app's own internal port is always fixed at `5560` and isn't user-configurable either way. Right after that, the **TLS certificate** choice appears — which options make sense depends on the "Existing nginx" choice above, which is why it's asked afterward. If nginx site configs on this host already reference one or more certificates, each shows up as its own "Use the certificate detected at..." option (picking one updates both `.env` and the new host nginx site to point at it); otherwise just **Generate one** (recommended — creates a local Certificate Authority the first time you use it, then issues a certificate for `WEB_FQDN` signed by that CA — no real domain or public CA needed. After saving you get a download link for the CA certificate; install it into your OS/browser trust store on any device that needs to reach the site without a warning — the CA's private key never leaves the server, and re-running the wizard reuses the same CA rather than generating a new one each time. If your browser blocks or won't complete that download, some, Brave included, are strict about this kind of download — the same file is always sitting on the host at `certs/ca.pem`, outside Docker entirely — copy it from there instead) and **I'll provide my own** (uses whatever you fill into `SSL_CERT_FILE`/`SSL_KEY_FILE` — as you type either path, the form checks live whether that file actually exists on the host running the wizard) are offered. `WEB_FQDN` starts blank (with a note suggesting the format, e.g. `fondwaypoints.example.com`) and is required — the field itself can't be empty. As you type, the form also checks live whether it actually resolves in DNS yet (an IP address always counts as resolving) — a green "DNS resolves for this address" note shows up if so, or a yellow note if not, saying setup can still proceed but it's recommended to have the name in DNS (or a hosts file entry) before you rely on it day-to-day. That DNS check is informational only and never blocks saving — getting the name into DNS is often something you finish after this step — but leaving the field itself blank does. Every field marked "Required" (the two secrets, `ADMIN_EMAIL`, `WEB_FQDN`, and `SSL_CERT_FILE`/`SSL_KEY_FILE` when "I'll provide my own" applies) shows that same green PASS treatment the moment it actually has something typed into it, instead of staying on the red "Required." note. Either way the wizard brings the app itself up before exiting — there's no separate "now run docker compose" step afterward. Re-running `./install.sh`/`./setup.sh` later reopens the wizard, prefilled with whatever's already in `.env`/`.deploy-state.json` (including which deploy mode you picked last time), so changing a setting is just editing the relevant field and resubmitting. To redeploy/restart with the existing settings unchanged instead — e.g. after a code update — use `./update.sh` (see [Updating](Updating.md)) rather than reopening the wizard. The setup wizard normally only listens on `127.0.0.1` and opens your browser automatically; if you're running it on a headless server it also prints a LAN URL you can open from another computer on the network (anyone on that network can reach the page, unauthenticated, until you submit the form — keep that window short). Once the app itself is up (Docker stack running, or the local service started), the script prints the URL(s) it's reachable at — `WEB_FQDN` plus this machine's LAN IP address(es) (loopback isn't listed, since nothing outside this machine can reach it there), on whichever port applies: `NGINX_HTTPS_PORT` in Docker mode, or the app's own fixed port (5560) in local mode. This also happens on a later re-run that skips reopening the wizard (redeploying from the existing `.env`). If "Existing nginx" was enabled, this changes: the fixed-port printout is skipped (it'd be plain HTTP at that point — not a secure context, so the app's client-side encryption wouldn't work there) in favor of `https://WEB_FQDN/` plus `https://:/` for each LAN IP, printed once that host nginx site is confirmed live. > **Import the certificate first.** Unless `SSL_CERT_FILE`/`SSL_KEY_FILE` point at a certificate from a > public CA (e.g. Let's Encrypt), your browser won't trust it yet — whether it's the one the wizard > generated (see [TLS certificate](#guided-setup-in-full) above) or one you supplied yourself. Install > the CA certificate (downloaded at the end of setup, for a generated one) — or your own self-signed/ > private CA — into the OS/browser trust store of every device that needs to reach the site, *before* > visiting it there. Skipping this gets a certificate warning on every visit, and some browsers or > managed devices refuse to proceed past it at all. Then visit `/auth/signup` on one of the printed URLs to create the first account. There is no CLI-based account bootstrap — account creation generates your encryption keys in the browser, which a server-side script can't do without seeing your password. ## Docker deployment, in full > **Running inside a Proxmox LXC container?** **Ubuntu LXC is not recommended and not supported** for > Docker deployment — running Docker nested inside an Ubuntu LXC guest reliably hits AppArmor > confinement failures (see below) that range from awkward to unfixable depending on the exact > kernel/runc combination, and we can't chase every version-specific variant of this. **If you're > deploying into an LXC container, use a Rocky Linux (or other RHEL-based) image instead** — RHEL-family > distros use SELinux rather than AppArmor and don't have this nested-confinement fragility; Rocky 9 is > one of this project's two actually-tested platforms (see the testing scope note above) and is the > recommended choice specifically for LXC deployment. Plain (non-LXC) Ubuntu — a real VM or bare metal — > is unaffected by any of this and works fine. > > If you still want to try Docker in an Ubuntu LXC anyway: **Nesting alone isn't enough** to run Docker > inside an unprivileged LXC — you'll also need **Keyctl**, and AppArmor confinement relaxed for the > container. Without all of this, containers fail to start with an `apparmor failed to apply profile` > error — often confusingly mid-`docker build`, on whichever `RUN` step happens to be the first one that > needs to actually start a fresh container on this host (previous steps can look fine if they hit > Docker's build cache). > > 1. In the Proxmox web UI: select the container → **Options** → **Features** → **Edit** → check both > **Nesting** and **Keyctl** → OK. (Or on the Proxmox host: `pct set --features nesting=1,keyctl=1`.) > 2. On the **Proxmox host** itself (not inside the container), edit `/etc/pve/lxc/.conf` and add: > ``` > lxc.apparmor.profile: unconfined > ``` > 3. Restart the container: `pct restart `. > > If you'd rather not loosen an unprivileged container's confinement this much, the simpler alternative > is to make the container **privileged** instead (usually easiest done by recreating it) — that avoids > these nesting/AppArmor issues entirely, at the cost of weaker isolation between the container and the > Proxmox host. > > **Still failing even on a privileged container?** You may be hitting a runc/AppArmor version > incompatibility on newer Ubuntu (24.04+) kernels — Docker (inside the container) still tries to apply > its own `docker-default` profile to every container/build step it creates, independent of the outer > LXC's own confinement, and that can fail with `apparmor failed to apply profile: ... no such file or > directory` if the installed runc doesn't yet support this kernel's AppArmor interface. See > [Disabling AppArmor on Ubuntu 24+](#disabling-apparmor-on-ubuntu-24-if-docker-cant-apply-its-profile) > below. ### Disabling AppArmor on Ubuntu 24+ (if Docker can't apply its profile) Ubuntu 24.04+ enabled AppArmor more aggressively than earlier releases, which is a common source of friction for Docker running inside a container (nested AppArmor confinement is fragile and very version-dependent — see the Proxmox LXC note above). If you're hitting `apparmor failed to apply profile` errors even after the LXC-level fixes, or you'd simply rather avoid this class of problem before it starts, AppArmor can be disabled outright: **On a real Ubuntu 24+ VM or bare-metal install** — this fully disables AppArmor at the kernel level, which is what actually matters (stopping the userspace service alone is not enough; the LSM itself initializes at boot from the kernel command line, before systemd runs): ```bash sudo systemctl disable --now apparmor.service sudo systemctl mask apparmor.service sudo sed -i 's/GRUB_CMDLINE_LINUX_DEFAULT="/GRUB_CMDLINE_LINUX_DEFAULT="apparmor=0 /' /etc/default/grub sudo update-grub sudo reboot ``` After rebooting, confirm it took effect: ```bash cat /sys/module/apparmor/parameters/enabled # should print N ``` **Inside an LXC container** (Proxmox, or similar): the guest doesn't have its own kernel — it shares the host's — so the `GRUB`/reboot step above does nothing from inside the container; the kernel-level AppArmor flag is controlled by the Proxmox **host**, not the guest. From inside the container you can still stop and mask the userspace service: ```bash sudo systemctl disable --now apparmor.service sudo systemctl mask apparmor.service ``` This removes the container's own AppArmor tooling/enforcement, but since the LSM is still active at the shared host kernel level, it may not resolve every variant of this error on its own. If it doesn't, the remaining option is disabling AppArmor the same way (kernel command line + reboot) on the **Proxmox host itself** — which affects every VM/container on that host, not just this one, so only do this if you're comfortable with that trade-off (or the host isn't running anything that depends on AppArmor confinement). TLS is handled by the nginx container bundled via the `docker-compose.nginx.yml` overlay — no separate reverse proxy needed in front of it. Point `SSL_CERT_FILE` / `SSL_KEY_FILE` in `.env` at a cert/key pair on the Docker host; the [guided setup](#guided-setup-in-full) wizard can generate one for you (a local CA plus a certificate for `WEB_FQDN`), or you can supply your own. (If an existing nginx is already running on this host instead, the guided setup's "Existing nginx" step — see [Guided setup, in full](#guided-setup-in-full) above — skips this overlay and exposes the app container's port directly for that nginx to front instead.) MongoDB is **not** bundled by `docker-compose.yml` itself — `docker-compose.mongo.yml` is an overlay that adds it. Include it only if you want the internal/bundled option. Similarly, the image published to a registry isn't referenced by `docker-compose.yml` either — that lives in the `docker-compose.pull.yml` overlay (see [Pulling a pre-built image](#pulling-a-pre-built-image-instead-of-building) below), so a plain build never contacts a registry: - Internal MongoDB: `docker compose -f docker-compose.yml -f docker-compose.nginx.yml -f docker-compose.mongo.yml up -d --build`, with `mongoHost=mongo` in `.env`. - External MongoDB: `docker compose -f docker-compose.yml -f docker-compose.nginx.yml up -d --build`, with `mongoHost` pointed at your server. ### Manual `.env` setup instead of the wizard 1. Copy `docker-compose.yml`, `docker-compose.nginx.yml`, `docker-compose.mongo.yml` (if using internal MongoDB), `docker-compose.pull.yml` (if pulling a pre-built image — see [below](#pulling-a-pre-built-image-instead-of-building)), and `.env.example` to the deploy host, `cp .env.example .env`, and fill it in: - `sessionSecret` / `PRELOGIN_SECRET` — generate each with `node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"` - `mongoHost` — `mongo` if using the bundled container (see above), otherwise your external server's host/IP. - `SMTP_*` — an SMTP relay for invite/access-request emails. For Gmail, create an [App Password](https://myaccount.google.com/apppasswords) (requires 2-Step Verification) and use that, not your normal password. If left blank, the app still works — emails are just logged instead of sent. - `ADMIN_EMAIL` — the email that should get admin on signup (optional, but needed to reach `/admin`). - `SSL_CERT_FILE` / `SSL_KEY_FILE` — your own cert/key pair, or generate a quick self-signed one: ``` openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes \ -keyout certs/ca.key -out certs/ca.pem -subj "/CN=Local CA" openssl req -newkey rsa:2048 -nodes -keyout certs/cert.key -out certs/cert.csr \ -subj "/CN=" openssl x509 -req -in certs/cert.csr -CA certs/ca.pem -CAkey certs/ca.key -CAcreateserial \ -out certs/cert.pem -days 825 -sha256 \ -extfile <(echo "subjectAltName=DNS:,IP:,IP:127.0.0.1") ``` then install `certs/ca.pem` into your OS/browser trust store on any device that needs to reach the site without a warning. Include an `IP:` SAN entry for every address (LAN IP, `127.0.0.1`, etc.) you might open the site by directly — a hostname-only cert throws a mismatch warning the moment someone visits by IP instead. This is exactly what the guided setup wizard automates. 2. `docker compose -f docker-compose.yml -f docker-compose.nginx.yml up -d --build` (add `-f docker-compose.mongo.yml` for internal MongoDB). This brings up the app plus the bundled nginx container, and, if you included the MongoDB overlay, a bundled MongoDB container (data persisted in a Docker volume), plus an `uploads-data` volume for encrypted file attachments. ### Pulling a pre-built image instead of building `docker compose up -d --build` above always builds from this checkout, and never references a registry image — `docker-compose.yml` has no `image:` key of its own. To pull a pre-built image instead, add the `docker-compose.pull.yml` overlay (which supplies that key) and drop `--build`: ``` docker compose -f docker-compose.yml -f docker-compose.nginx.yml -f docker-compose.pull.yml pull docker compose -f docker-compose.yml -f docker-compose.nginx.yml -f docker-compose.pull.yml up -d # add -f docker-compose.mongo.yml too if you also want internal MongoDB ``` `FONDWAYPOINTS_IMAGE` in `.env` has no default — this project doesn't publish a pre-built image itself (see [CI/CD and Releases](CI-CD-and-Releases.md)) — set it to wherever you build/push your own (e.g. your own Gitea instance's container registry). This is a manual alternative to the guided setup, which always builds locally instead — see [Why build locally instead of pulling an image?](https://github.com/j5guy/fondwaypoints#why-build-locally-instead-of-pulling-an-image) in the README. Note that this still uses `docker-compose.yml`/`docker-compose.pull.yml` from a checkout — it just skips the *build* step by pulling the image instead. If you don't want a checkout at all, see [Just the image, no repo checkout](#just-the-image-no-repo-checkout) below. ## Just the image, no repo checkout **[Guided setup, in full](#guided-setup-in-full) above is the quickest and easiest way to get running** — use this section instead only if you specifically want to skip the repo checkout. Everything above — the guided setup wizard, this repo's own `docker-compose.yml`, the bundled nginx container — is a convenience that comes with a checkout, not a prerequisite, but this path is more manual since none of that automation is there to do it for you. On any machine that already has Docker installed, running the app is three things: pull the image, write a `.env`, write a small `docker-compose.yml` of your own to bring up the stack. No `git clone`, no build. **This project doesn't publish a pre-built image itself** (see [CI/CD and Releases](CI-CD-and-Releases.md)) — this section assumes you've built and pushed your own to a registry you control (e.g. your own Gitea instance's container registry) and are pulling that. Replace `/fondwaypoints:latest` below with the actual path. If you don't have one, use the [guided setup](#guided-setup-in-full) instead, which always builds locally. TLS is mandatory (see above) but doesn't require the repo either — `nginx:alpine` is just a public image, so a TLS-terminating reverse proxy can ride in the same self-authored `docker-compose.yml`. ```bash docker pull /fondwaypoints:latest ``` Generate a self-signed cert/key (or drop in your own), and a browser/OS will only trust it once you install `cert.pem` into its trust store — or use a real cert from a public CA instead and skip this: ```bash openssl req -x509 -newkey rsa:4096 -sha256 -days 825 -nodes \ -keyout cert.key -out cert.pem -subj "/CN=fondwaypoints.example.com" \ -addext "subjectAltName=DNS:fondwaypoints.example.com,IP:127.0.0.1" ``` Write an `nginx.conf` next to it: ``` server { listen 80; server_name _; return 301 https://$host:5560$request_uri; } server { listen 5560 ssl; server_name _; client_max_body_size 150M; ssl_certificate /etc/nginx/certs/server.pem; ssl_certificate_key /etc/nginx/certs/server.key; ssl_protocols TLSv1.2 TLSv1.3; location / { proxy_pass http://app:5560; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Port $server_port; } } ``` A `.env` — generate `sessionSecret`/`PRELOGIN_SECRET` with `openssl rand -hex 64` (the same command the setup wizard's "generate" buttons run): ``` WEB_FQDN=fondwaypoints.example.com sessionSecret=<64 random hex bytes> PRELOGIN_SECRET=<64 random hex bytes> mongoHost=mongo mongoUser= mongoPass= mongoPort=27017 mongoDBName=fondwaypoints ADMIN_EMAIL= SIGNUP_ALLOWLIST= SMTP_HOST=smtp.gmail.com SMTP_PORT=465 SMTP_USER= SMTP_PASS= SMTP_FROM=Fond Waypoints DEFAULT_WAIT_DAYS=14 ``` And a `docker-compose.yml` tying it all together: ```yaml services: app: image: /fondwaypoints:latest restart: unless-stopped env_file: - .env volumes: - uploads-data:/app/public/uploads - logs-data:/app/logs depends_on: mongo: condition: service_healthy nginx: image: nginx:alpine restart: unless-stopped depends_on: - app ports: - "80:80" - "5560:5560" volumes: - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro - ./cert.pem:/etc/nginx/certs/server.pem:ro - ./cert.key:/etc/nginx/certs/server.key:ro mongo: image: mongo:7 restart: unless-stopped volumes: - mongo-data:/data/db healthcheck: test: ["CMD", "mongosh", "--quiet", "--eval", "db.adminCommand('ping')"] interval: 5s timeout: 5s retries: 20 volumes: uploads-data: logs-data: mongo-data: ``` Then `docker compose up -d`. Note that `app` has no `ports:` of its own — `nginx` is the only thing reachable from outside, at `80`/`5560` on whichever host runs this. Only bind those to `127.0.0.1` instead if you specifically want to restrict them to local-host-only reachability (e.g. because something else in front of `nginx` handles the actual public-facing side). > **Already have something else on port 80 or 5560?** `nginx` here will fail to start with a "port is > already allocated" error — nothing else breaks, but this stack's proxy won't come up until it's > resolved. Either change the host-side port in `ports:` (e.g. `"8443:5560"` — only the number *before* > the colon needs to change, the container-side `5560` must stay put to match `nginx.conf`), or, if > the other container is itself a reverse proxy, skip publishing a port here entirely and join this > `nginx` service to that proxy's Docker network instead so it can route to `fondwaypoints-nginx:5560` > directly. Already have a MongoDB server elsewhere? Delete the `mongo` service and the `depends_on` block on `app`, and point `mongoHost` (plus `mongoUser`/`mongoPass` if it needs auth) at that server instead. Already have your own reverse proxy (or don't want nginx bundled in)? Delete the `nginx` service, add `ports: ["5560:5560"]` to `app` instead, and point your proxy's TLS termination at that. Then visit `/auth/signup` once it's reachable to create the first account. See [`.env.example`](https://github.com/j5guy/fondwaypoints/blob/master/.env.example) on GitHub for the full list of environment variables and what each does — `ADMIN_EMAIL`/ `SIGNUP_ALLOWLIST` (who's allowed to sign up) and `SMTP_*` (invite/access-request emails; the app still works with these blank, it just logs emails instead of sending them) are the ones most worth reading before you go live. ## Manual / non-Docker deployment, in full The [guided setup](#guided-setup-in-full) wizard's "Local Node.js process" option automates the steps below (npm install/build, `.env`, and the systemd service) — this section is for setting it up by hand instead, or for understanding what the wizard did. MongoDB isn't bundled this way — point `mongoHost` in `.env` at an existing server, or run a standalone container for it: ``` docker run -d --name fondwaypoints-mongo --restart unless-stopped \ -p 127.0.0.1:27017:27017 -v fondwaypoints-mongo-data:/data/db mongo:7 ``` Then: ``` npm install npm run build-css npm start ``` See `.env.example` for the full list of environment variables.