Repository navigation
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/.
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 -dThen 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 |
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 onlyDATABASE_URL,ADMIN_PASSWORDandNODE_ENV, so aFLIGHT_API_KEYset 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.
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:3000npm 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.
Setting it up
Using the admin panel
- Guests and RSVPs
- Seating and the Export
- Registry
- Finances
- Honeymoon Portal
- Content and Settings
- Offline
How it works