Skip to content

Installation

Austin Stanfield edited this page Aug 26, 2026 · 5 revisions

Installation

Portainer, plain Docker Compose, or running it locally.

The image is published to GitHub Container Registry on every merge to main, so there is nothing to build. Everything Docker-related lives in docker/.

Docker Compose, from the repository

The shortest route, and the one the README quotes:

git clone https://github.com/Soccerbeats/weddingwebsite.git
cd weddingwebsite
cp docker/.env.example docker/.env     # set POSTGRES_PASSWORD and ADMIN_PASSWORD
docker compose -f docker/docker-compose.prod.yml up -d

Then http://localhost:3000, and /admin with the password you set.

Three compose files, and it matters which:

File What it is
docker/docker-compose.prod.yml Production. Pulls the published image
docker/docker-compose.dev.yml Development. Builds from source
docker/docker-compose.demo.yml The read-only demo — see The demo instance

Portainer

The easiest way to self-host. No cloning required — Portainer pulls the pre-built image from GitHub Container Registry.

1. Open Portainer → Stacks → Add Stack

Give it a name (e.g. wedding) and paste the following into the Web editor:

services:
  web:
    image: ghcr.io/soccerbeats/weddingwebsite:latest
    container_name: wedding-web-prod
    restart: always
    ports:
      - '3000:3000'
    environment:
      - DATABASE_URL=${DATABASE_URL}
      - ADMIN_PASSWORD=${ADMIN_PASSWORD}
      - NODE_ENV=production
      # Optional, and all `:-` so an unset one is an empty string rather than a
      # compose warning. A variable NOT listed here never reaches the container,
      # however carefully it is set on the stack — see the note under the table.
      - JWT_SECRET=${JWT_SECRET:-}
      - FLIGHT_API_KEY=${FLIGHT_API_KEY:-}
      - FLIGHT_API_HOST=${FLIGHT_API_HOST:-}
      - GEOCODER_USER_AGENT=${GEOCODER_USER_AGENT:-}
      - NOMINATIM_URL=${NOMINATIM_URL:-}
      - OSRM_URL=${OSRM_URL:-}
      - SMTP_HOST=${SMTP_HOST:-}
      - SMTP_PORT=${SMTP_PORT:-}
      - SMTP_USER=${SMTP_USER:-}
      - SMTP_PASS=${SMTP_PASS:-}
      - NOTIFICATION_EMAIL=${NOTIFICATION_EMAIL:-}
    volumes:
      - photos_data:/app/public/photos
      - config_data:/app/public/config
    depends_on:
      - db

  db:
    image: postgres:15-alpine
    container_name: wedding-db-prod
    restart: always
    environment:
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_DB=${POSTGRES_DB}
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:
  photos_data:
  config_data:

2. Set environment variables in the Portainer UI under the compose editor (Environment variables section):

Variable Example value Notes
ADMIN_PASSWORD yourStrongPassword! Password for /admin login
POSTGRES_USER wedding_user Database username
POSTGRES_PASSWORD yourDbPassword! Database password
POSTGRES_DB wedding_db Database name
DATABASE_URL postgresql://wedding_user:yourDbPassword!@db:5432/wedding_db Must match the three values above

Those five are all it needs. The rest are optional, and every feature that uses one says so in the UI when it is missing:

Variable Example value Turns on
JWT_SECRET a long random string Signs the admin cookie independently of the password, so rotating one does not invalidate the other
FLIGHT_API_KEY a RapidAPI key Fill in from schedule on a travel leg. Subscribe to AeroDataBox; its free plan allows one request a second
GEOCODER_USER_AGENT WeddingWebsite/1.0 (you@example.com) Identifies you to OpenStreetMap's geocoder, which answers 403 Access denied to callers it cannot contact. Worth setting — without it map search on the server can fail while working on your laptop
NOMINATIM_URL / OSRM_URL https://nominatim.example.com/search Your own geocoder / routing server instead of the public ones
SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS, NOTIFICATION_EMAIL your mail provider's An email when an RSVP arrives

The trap worth knowing (fixed in v0.9.51): a variable set on the stack only reaches the app if the compose file also lists it under environment:. Before v0.9.51 the published compose passed only DATABASE_URL, ADMIN_PASSWORD and NODE_ENV, so a FLIGHT_API_KEY set correctly on the stack never arrived and the feature reported itself unconfigured — and the documented SMTP settings could never have worked at all. If you are updating an older stack, re-paste the compose above. To check what a running container actually has: docker exec wedding-web-prod printenv FLIGHT_API_KEY.

3. Deploy the stack — Portainer pulls the image and starts both containers. The database schema initialises automatically on first boot.

4. Access the site:

  • Public site: http://your-server-ip:3000
  • Admin panel: http://your-server-ip:3000/admin

Tip: Put a reverse proxy (Nginx Proxy Manager, Traefik, Caddy) in front of port 3000 to serve over HTTPS on a custom domain.


Local Development

git clone https://github.com/Soccerbeats/weddingwebsite.git
cd weddingwebsite
npm install

# A Postgres to point at — anything will do
docker run -d --name wed-db -e POSTGRES_USER=wed -e POSTGRES_PASSWORD=wed \
  -e POSTGRES_DB=wed -p 5432:5432 postgres:15-alpine

DATABASE_URL=postgresql://wed:wed@localhost:5432/wed ADMIN_PASSWORD=dev npm run dev
# open http://localhost:3000

npm run dev doesn't apply the schema (the container does that on boot), so apply it once by hand: psql … < database/init.sql. More in Development.


← All pages

Clone this wiki locally