Skip to content

Installation Guide

j5guy edited this page Jul 25, 2026 · 4 revisions

Installation Guide

For the quick-start commands, see 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. Already installed and want to update instead? See Updating.

The guided setup 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.

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:

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 below). It handles a lot of environment variation automatically and won't be perfect everywhere — please report anything that breaks with your OS/version and what happened. See What the Setup Script Does for exactly what it does, and Manual / non-Docker deployment 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? in the README (this project doesn't publish a pre-built image itself — see CI/CD and Releases) — 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 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? 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 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 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://<lan-ip>:<that port>/, 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) 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://<lan-ip>:<HOST_NGINX_IP_PORT>/ 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 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 → OptionsFeaturesEdit → check both Nesting and Keyctl → OK. (Or on the Proxmox host: pct set <CTID> --features nesting=1,keyctl=1.)
  2. On the Proxmox host itself (not inside the container), edit /etc/pve/lxc/<CTID>.conf and add:
    lxc.apparmor.profile: unconfined
    
  3. Restart the container: pct restart <CTID>.

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+ 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):

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:

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:

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 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 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 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), 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'))"
    • mongoHostmongo 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 (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=<your WEB_FQDN>"
      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:<your WEB_FQDN>,IP:<your server's LAN 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) — 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? 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 below.

Just the image, no repo checkout

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) — 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 <your-registry>/fondwaypoints:latest below with the actual path. If you don't have one, use the guided setup 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.

docker pull <your-registry>/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:

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 <your-address@gmail.com>
DEFAULT_WAIT_DAYS=14

And a docker-compose.yml tying it all together:

services:
  app:
    image: <your-registry>/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 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 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.

Clone this wiki locally