Self-host SavSpot

Run your own instance with Docker. Full source code, no limits, no fees. One command to get started.

Quick Start

Clone the repo and run the install script. That's it.

terminal
$ git clone https://github.com/stephendeslate/savspot.git
$ cd savspot
$ ./scripts/install.sh

The install script will:

  1. Create .env from the production template
  2. Generate JWT keys and an encryption key
  3. Generate a random database password
  4. Build all containers
  5. Run database migrations
  6. Start the full stack

SavSpot will be available at http://localhost (or https://yourdomain.com if DOMAIN is configured).

Prerequisites

Docker 24+

With Compose v2 (docker compose)

2 GB RAM minimum

4 GB recommended for production

Linux server

Ubuntu 22.04+, Debian 12+, or similar

Domain name (optional)

Works on localhost for testing

Manual Setup

If you prefer to set things up yourself:

terminal
$ git clone https://github.com/stephendeslate/savspot.git
$ cd savspot

# Create and edit your environment file
$ cp .env.production.example .env

# Generate keys (append to .env)
$ ./scripts/generate-keys.sh >> .env

# Edit .env — at minimum, set:
#   POSTGRES_PASSWORD (strong random password)
#   DOMAIN (your domain, or "localhost" for testing)

# Build and start
$ docker compose -f docker-compose.prod.yml up -d

Configuration Reference

Required Variables

VariableDescription
POSTGRES_PASSWORDDatabase password (auto-generated by install script)
JWT_PRIVATE_KEY_BASE64RS256 private key, base64-encoded
JWT_PUBLIC_KEY_BASE64RS256 public key, base64-encoded
ENCRYPTION_KEY32-byte hex key for encrypting sensitive data

Domain & URLs

VariableDefaultDescription
DOMAINlocalhostYour domain — Caddy auto-provisions HTTPS via Let's Encrypt
WEB_URLhttp://localhostPublic URL (used for email links, CORS)

Optional Integrations

All integrations gracefully degrade — SavSpot runs without any of them.

IntegrationVariablesWithout it
Email (Resend)RESEND_API_KEYEmails logged to console
Payments (Stripe)STRIPE_SECRET_KEYPayment features disabled
File Uploads (R2)R2_ACCOUNT_ID, etc.Upload endpoints return errors
Google OAuthGOOGLE_CLIENT_IDGoogle login button hidden

Architecture

Six services, one docker compose command.

┌─────────────┐
│   Caddy      │  :80/:443 — auto HTTPS, reverse proxy
├─────────────┤
│   Web        │  :3000 — Next.js frontend (standalone)
│   API        │  :3001 — NestJS backend
│   Worker     │  BullMQ background jobs
├─────────────┤
│   PostgreSQL │  :5432 — data store with RLS
│   Redis      │  :6379 — cache + job queue
└─────────────┘

Caddy routes /api/* to the API and everything else to the Web frontend. It handles TLS certificates automatically when DOMAIN is set to a real domain.

Common Operations

View logs

terminal
$ docker compose -f docker-compose.prod.yml logs -f        # All services
$ docker compose -f docker-compose.prod.yml logs -f api     # API only
$ docker compose -f docker-compose.prod.yml logs -f web     # Web only

Stop / Start

terminal
$ docker compose -f docker-compose.prod.yml down            # Stop all
$ docker compose -f docker-compose.prod.yml up -d           # Start all

Update to latest version

terminal
$ git pull
$ docker compose -f docker-compose.prod.yml build
$ docker compose -f docker-compose.prod.yml up -d

Migrations run automatically on startup via the migrate service.

Database backup & restore

backup
$ docker compose -f docker-compose.prod.yml exec postgres \
    pg_dump -U savspot savspot > backup-$(date +%Y%m%d).sql
restore
$ docker compose -f docker-compose.prod.yml exec -T postgres \
    psql -U savspot savspot < backup-20260316.sql

Custom Domain with HTTPS

Caddy automatically obtains and renews Let's Encrypt certificates — no manual TLS configuration needed.

  1. Point your domain's DNS A record to your server's IP address
  2. Set DOMAIN=yourdomain.com in .env
  3. Set WEB_URL=https://yourdomain.com in .env
  4. Restart: docker compose -f docker-compose.prod.yml up -d

Troubleshooting

Containers won't start

terminal
# Check logs for errors
$ docker compose -f docker-compose.prod.yml logs

# Common issue: port 80/443 already in use
$ sudo lsof -i :80
$ sudo lsof -i :443

API health check failing

terminal
# Check if migrations ran successfully
$ docker compose -f docker-compose.prod.yml logs migrate

# Check API logs
$ docker compose -f docker-compose.prod.yml logs api

Database connection errors

terminal
# Verify postgres is healthy
$ docker compose -f docker-compose.prod.yml exec postgres pg_isready

# Check the DATABASE_URL matches POSTGRES_USER/PASSWORD/DB in .env

Web app showing blank page

terminal
# Check if NEXT_PUBLIC_API_URL is reachable from the web container
$ docker compose -f docker-compose.prod.yml exec web \
    wget -q --spider http://api:3001/health

Hardware Recommendations

Rough estimates. Actual requirements depend on booking volume and feature usage.

UsersRAMCPUDisk
1–502 GB1 core10 GB
50–5004 GB2 cores20 GB
500+8 GB+4 cores50 GB+

Enterprise Features

Self-hosted users can unlock enterprise features (audit logging, workflows, contracts, multi-location, and more) by setting a license key:

.env
SAVSPOT_LICENSE_KEY=your-license-key-here

Without a license key, SavSpot runs with the full AGPL core — every essential feature for running a booking business, with no limits. Enterprise features are available on managed cloud plans or with a self-hosted license key.

Ready to self-host?

Clone the repo and run one command. Or try our managed cloud with a 14-day free trial.