Skip to content

Deploy with Docker Compose

Deploy FastSvelte on your own server using Docker Compose for full control over your infrastructure.

What You'll Need

  • A Linux server (Ubuntu 22.04+ recommended)
  • Docker and Docker Compose installed
  • A domain name (optional but recommended)
  • Basic Linux and DevOps knowledge

Cost Estimate

  • VPS (2GB RAM): $5-12/month
  • Domain: $10-15/year
  • Total: ~$5-15/month
Why Docker Compose?
  • Full control: your server, your rules
  • Cost-effective: the cheapest option for a single deployment
  • Simple: one configuration file
  • Portable: works on any Docker host
  • No vendor lock-in: standard Docker setup

Prerequisites

# Install Docker
curl -fsSL https://get.docker.com | sh

# Install Docker Compose
sudo apt-get update
sudo apt-get install docker-compose-plugin

The Backend Runs in Docker Compose

The kit ships backend/docker-compose.yml, which runs the two server pieces: PostgreSQL and the FastAPI backend.

# backend/docker-compose.yml (shipped with the kit, simplified)
services:
  db:
    image: postgres:17
    volumes:
      - db-data:/var/lib/postgresql/data

  api:
    build:
      context: .
    depends_on:
      - db
    ports:
      - "8000:3100"
    env_file:
      - .env

The frontend and landing are not compose services. They are static builds (see Architecture): the app is an SPA with an index.html fallback, the landing is prerendered HTML. Deploy the Frontends below covers serving them.

Deployment Steps

1. Clone Your Repository

git clone <your-fastsvelte-repo>
cd fastsvelte

2. Configure Environment

cd backend
cp .env.example .env
# Fill in the secrets: database credentials, session secret, Stripe keys, ...

3. Deploy

# Start db + api (from backend/)
docker compose up -d

# View logs
docker compose logs -f

4. Run Migrations

# Access backend container
docker-compose exec api bash

# Run migrations
cd /app
# Your migration commands here

Deploy the Frontends (Static Files)

frontend/ and landing/ build to plain static files, and PUBLIC_* variables are baked in at build time, so set them before npm run build. There are two ways to serve the output; Option A is simpler.

Build on the server (Node 24+) or in CI, then copy the output into place:

sudo mkdir -p /var/www/fastsvelte/app /var/www/fastsvelte/landing

cd frontend
npm ci
PUBLIC_API_BASE_URL=https://api.yourdomain.com npm run build
sudo cp -r build/. /var/www/fastsvelte/app/

cd ../landing
npm ci
npm run build
sudo cp -r build/. /var/www/fastsvelte/landing/

Rebuild and re-copy whenever the code or any PUBLIC_* value changes. The landing is prerendered, so marketing content edits also need a rebuild.

Option B: Frontends in Docker

Prefer everything containerized? Create frontend/Dockerfile (the kit does not ship one):

FROM node:24-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
ARG PUBLIC_API_BASE_URL
ENV PUBLIC_API_BASE_URL=$PUBLIC_API_BASE_URL
RUN npm run build

FROM nginx:alpine
COPY --from=build /app/build /usr/share/nginx/html
COPY <<'EOF' /etc/nginx/conf.d/default.conf
server {
    listen 80;
    root /usr/share/nginx/html;
    try_files $uri /index.html;
}
EOF

PUBLIC_API_BASE_URL must be a build arg (under build.args in your compose service), not a runtime variable: a static build cannot read the environment after it is built. Repeat for landing/, changing the nginx line to try_files $uri $uri.html =404; (the landing is prerendered HTML, not an SPA). With this option, the nginx blocks below proxy_pass to the containers instead of serving files.

Setting Up Nginx

nginx fronts everything: it proxies the API container and, with Option A, serves the static builds directly:

# /etc/nginx/sites-available/fastsvelte
server {
    server_name api.yourdomain.com;

    location / {
        proxy_pass http://localhost:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

# App (static SPA): serve files, fall back to index.html for deep links
server {
    server_name app.yourdomain.com;
    root /var/www/fastsvelte/app;
    try_files $uri /index.html;
}

# Landing (prerendered): serve files, map /about to about.html
server {
    server_name yourdomain.com;
    root /var/www/fastsvelte/landing;
    try_files $uri $uri.html =404;
}

With Option B, replace the two static blocks with proxy_pass blocks pointing at the frontend and landing containers' published ports.

Security headers

In the app.yourdomain.com server block above, add the app's security headers:

server {
    server_name app.yourdomain.com;

    add_header Content-Security-Policy "default-src 'self'; connect-src 'self' https://api.yourdomain.com; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self'; font-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;

    root /var/www/fastsvelte/app;
    try_files $uri /index.html;
}

Replace https://api.yourdomain.com in connect-src with your real API URL, or the browser will block the app from calling it. See Security.

Serving the app from a sub-path

Sub-path serving is simplest with Option B, where the app container keeps serving the build at its root. To serve the app at yourdomain.com/app instead of its own subdomain, drop the app.yourdomain.com server block and add a location to the main site's block that proxies to the app container:

location /app/ {
    proxy_pass http://localhost:80/;  # trailing slash strips the /app prefix
    proxy_set_header Host $host;
}

The container keeps serving the build at its root with its own index.html fallback; the stripped prefix makes that work unchanged. The frontend and backend settings that go with this are in Serving from a Sub-Path.

SSL with Let's Encrypt

# Install Certbot
sudo apt-get install certbot python3-certbot-nginx

# Get certificates
sudo certbot --nginx -d api.yourdomain.com -d app.yourdomain.com -d yourdomain.com

Maintenance

Backups

# Backup database
docker-compose exec db pg_dump -U postgres fastsvelte > backup.sql

# Backup volumes
docker run --rm -v fastsvelte_postgres_data:/data -v $(pwd):/backup ubuntu tar czf /backup/postgres_backup.tar.gz /data

Updates

# Pull latest changes
git pull

# Rebuild and restart the backend
(cd backend && docker compose up -d --build)

# Rebuild and redeploy the frontends (Option A)
(cd frontend && npm run build) && sudo cp -r frontend/build/. /var/www/fastsvelte/app/
(cd landing && npm run build) && sudo cp -r landing/build/. /var/www/fastsvelte/landing/

Monitoring

# View logs
docker-compose logs -f

# Check container status
docker-compose ps

# View resource usage
docker stats
  • Hetzner - €4-20/month, excellent EU performance
  • DigitalOcean - $6-20/month, reliable and well-documented
  • Vultr - $6-20/month, global locations
  • Linode - $5-20/month, good performance

Alternative: Use Coolify

If you want a GUI for managing Docker deployments, consider Coolify, a self-hostable web interface for Docker Compose deployments.

Security Considerations

  • Keep Docker and system packages updated
  • Use strong passwords and secrets
  • Configure firewall (ufw or iptables)
  • Set up automated backups
  • Monitor logs for suspicious activity
  • Use fail2ban for SSH protection

Docker Compose is best for: single deployments, learning, development servers, or when you want maximum control at minimum cost.