Lightweight wedding website that you host
4.6K
Eternal Vows is a customizable, self‑hosted wedding website template for sharing all of your event information in one place. Configure names, date, location, story, schedule, venue details (with map), registry links, FAQs, and optional photo‑sharing links via a simple JSON file—no rebuild required. A lightweight Node + Express server powers a dynamic background slideshow that auto‑refreshes as you add images. Run locally with Node or deploy easily with Docker/Compose.
config/photos (no rebuild required)Below are screenshots of the default layout to help visualize the sections and spacing:



index.html — UI, styling, and client JS (loads details from config/config.json)server.mjs — Express server and /api/photos endpointconfig/config.json — Site content (names, date, venue(s), etc.)config/photos/ — Background slideshow images.github/workflows/docker.yml — Optional CI to build/push a container imageRequirements: Node 18+ recommended.
npm install
node .\server.mjs
# then open http://localhost:5500
Optional: change the port via PORT env var (default 5500).
$env:PORT=8080; node .\server.mjs
Edit config/config.json. All fields are optional; unset sections/buttons are hidden automatically.
coupleNames: Display names for the hero sectiondateDisplay: Friendly wedding date stringlocationShort: Short city/state line under the datestory: Paragraph for “Our Story”schedule: Array of items { time, title, details }venues: Multiple venues (preferred). Use an array [{...}, {...}] or a labeled object map.
{ name, address, mapUrl, mapCta, notes, label? }{ ceremony: {...}, reception: {...} }venue: Legacy single-venue (optional): { name, address, mapUrl, mapCta, notes }photoUpload: { url, label?, text? } link to your shared album; text customizes the paragraph in the section (fallbacks to defaults if omitted)registry: Array of { label, url }faqs: Array of { q, a }slideshow: { intervalMs, transitionMs, photoRefreshSeconds, dynamicPhotosUrl } (defaults work out of the box)ui: { monogram, footerNote, autoRefreshSeconds }
ui.colors or top-level colors.
accent1, accent2, accent3, text, ink, bgOverlay, border, card, maxw, blur.blur (e.g., saturate(140%) blur(6px)) and unit values for maxw (e.g., 1024px).Example (default with multiple venues):
{
"coupleNames": "Partner One & Partner Two",
"dateDisplay": "Month 00, 20XX",
"locationShort": "City, ST",
"story": "Share a short story about how you met...",
"schedule": [
{ "time": "3:00 PM", "title": "Ceremony", "details": "Ceremony location details" },
{ "time": "4:00 PM", "title": "Cocktail Hour", "details": "Cocktail hour location details" },
{ "time": "5:30 PM", "title": "Reception", "details": "Reception location details" }
],
"venues": [
{ "label": "Ceremony", "name": "St. Mary Church", "address": "1 Church Rd", "mapUrl": "https://maps.google.com/?q=church", "notes": "Street parking available." },
{ "label": "Reception", "name": "The Grand Hall", "address": "999 Party Ave", "mapUrl": "https://maps.google.com/?q=hall", "notes": "Valet on site." }
],
"photoUpload": { "label": "Upload to Shared Album", "url": "https://example.com/album" },
"registry": [
{ "label": "Amazon", "url": "https://example.com/amazon" },
{ "label": "Target", "url": "https://example.com/target" }
],
"faqs": [ { "q": "Dress code?", "a": "Semi-formal." } ],
"slideshow": { "dynamicPhotosUrl": "/api/photos", "intervalMs": 6000, "transitionMs": 1200 },
"ui": {
"monogram": "You’re invited to the wedding of",
"footerNote": "With love, ...",
"colors": {
"accent1": "#a3bcd6",
"accent2": "#d7e5f3",
"accent3": "#f7eddc",
"text": "#ffffff",
"ink": "#2b2a2a",
"bgOverlay": "rgba(20,18,18,0.35)",
"border": "rgba(255,255,255,0.12)",
"card": "rgba(255,255,255,0.08)",
"maxw": "1024px",
"blur": "saturate(140%) blur(6px)"
}
}
}
Alternative formats for venues:
Legacy single venue (still supported):
"venue": {
"name": "Venue Name",
"address": "123 Main St, City, ST 00000",
"mapUrl": "https://maps.google.com",
"mapCta": "Open Map",
"notes": "Parking and arrival notes."
}
Object map (labels from keys):
"venues": {
"Ceremony": {
"name": "Ceremony Venue Name",
"address": "123 Chapel St, City, ST 00000",
"mapUrl": "https://maps.google.com/?q=Ceremony+Venue",
"mapCta": "Open Map",
"notes": "Arrive 15 minutes early. Street parking available."
},
"Reception": {
"name": "Reception Venue Name",
"address": "999 Grand Ave, City, ST 00000",
"mapUrl": "https://maps.google.com/?q=Reception+Venue",
"mapCta": "Open Map",
"notes": "Valet available. Dinner served at 6:00 PM."
}
},
config/photos/..jpg, .jpeg, .png, .webp, .avif./api/photos. The client preloads the first image quickly and crossfades.slideshow.photoRefreshSeconds seconds (default 20) to pick up new files.Tips:
Build a local image and run it. Mount your local config directory so you can edit content and photos without rebuilding.
Windows PowerShell:
docker build -t ghcr.io/jacoknapp/eternalvows:latest .
docker run --rm -p 5500:5500 -v ${PWD}\config:/app/config ghcr.io/jacoknapp/eternalvows:latest
macOS/Linux:
docker build -t ghcr.io/jacoknapp/eternalvows:latest .
docker run --rm -p 5500:5500 -v "$PWD/config:/app/config" ghcr.io/jacoknapp/eternalvows:latest
Optional: change the port with -e PORT=8080 -p 8080:8080.
Example docker-compose.yml if you prefer compose:
services:
wedding:
image: ghcr.io/jacoknapp/eternalvows:latest # or build: .
ports:
- "5500:5500"
environment:
- PORT=5500
volumes:
- ./config:/app/config
restart: unless-stopped
/.github/workflows/docker.yml builds a multi-arch image and can push to GHCR (GitHub Container Registry). You can enable Docker Hub pushes by adding a login step and including a docker.io image reference in the metadata step.
GET /api/photos → { files: ["file1.jpg", "file2.png", ...] } from config/photos/.npm install first to ensure dependencies are present.config/config.json is valid JSON.config/ and config/photos/ folders exist and are readable.PORT or stop the other process.config/photos with supported extensions.http://localhost:5500/api/photos returns a list.npm install again; Sharp provides prebuilt binaries for most Node versions.Keep private details out of version control. The provided config/config.json has placeholders—replace them locally or configure secrets in your deployment environment.
/favicon.wedding_bell.svg. If you want the bell larger, edit that SVG (we scale it around center) and then regenerate the raster variants as needed.site.webmanifest.Content type
Image
Digest
sha256:0e23f7c58…
Size
73.4 MB
Last updated
2 days ago
docker pull jacoknapp/eternalvows