Production-ready, fully containerised boilerplate for SvelteKit applications.
| Layer | Technology |
|---|---|
| Runtime & package manager | Bun |
| Framework | SvelteKit + Svelte 5 |
| Database | PostgreSQL 18 |
| ORM | Drizzle |
| Auth | Better Auth + admin plugin |
| Styling | Tailwind CSS v4 (OKLCH theming) |
| Unit tests | Vitest |
| E2E tests | Playwright |
| Local dev | VS Code DevContainers |
| Reverse proxy | Caddy (auto-HTTPS) |
| CI/CD | GitHub Actions → SSH deploy |
.
├── .devcontainer/ # VS Code DevContainer config (dev only)
├── .github/workflows/ # GitHub Actions CI/CD pipeline
├── drizzle/ # Generated SQL migrations (committed)
├── src/
│ ├── app.css # Tailwind v4 CSS-first config + OKLCH theme
│ ├── app.html # HTML shell (anti-FOUC theme script lives here)
│ ├── hooks.server.ts # Auth session injection + Better Auth route handler
│ ├── lib/
│ │ ├── auth-client.ts # Client-side Better Auth instance
│ │ └── server/
│ │ ├── auth.ts # Server-side Better Auth instance
│ │ ├── db.ts # Drizzle ORM client
│ │ ├── schema.ts # PostgreSQL schema (incl. Better Auth tables)
│ │ └── seed.ts # Admin user seed script
│ └── routes/ # SvelteKit file-based router
├── tests/
│ ├── e2e/ # Playwright end-to-end tests
│ └── unit/ # Vitest unit tests
├── Caddyfile # Caddy reverse-proxy + auto-HTTPS config
├── docker-compose.yml # Production stack (app + db + caddy)
├── Dockerfile # Multi-stage Bun build
├── drizzle.config.ts
├── playwright.config.ts
└── vite.config.ts # Vite + Vitest config
# 1. Clone the repository
git clone https://github.com/your-org/svelte-skeleton.git
cd svelte-skeleton
# 2. Copy environment file
cp .env.example .env
# Edit .env — at minimum set BETTER_AUTH_SECRET to a random string:
openssl rand -base64 32
# 3. Open in VS Code
code .
# When prompted: "Reopen in Container" — VS Code will build the dev containers,
# install dependencies, push the schema, and seed the admin user automatically.
# (postCreateCommand in devcontainer.json)
# 4. Start the dev server inside the container terminal
bun run dev
# → http://localhost:5173
# 1. Clone and enter the project
git clone https://github.com/your-org/svelte-skeleton.git
cd svelte-skeleton
# 2. Copy and edit environment variables
cp .env.example .env
# 3. Start a local PostgreSQL instance (Docker)
docker run -d \
--name svelte-skeleton-db \
-e POSTGRES_USER=postgres \
-e POSTGRES_PASSWORD=devpassword \
-e POSTGRES_DB=appdb \
-p 5432:5432 \
postgres:18-alpine
# 4. Install dependencies
bun install
# 5. Push schema to the database (creates all tables)
bun run db:push
# 6. Seed the admin user
bun run db:seed
# 7. Start the development server
bun run dev
# → http://localhost:5173
Copy .env.example to .env and fill in the values:
| Variable | Required | Description |
|---|---|---|
DATABASE_URL |
Yes | Full PostgreSQL connection string |
BETTER_AUTH_SECRET |
Yes | ≥32-char random secret. Generate: openssl rand -base64 32 |
BETTER_AUTH_URL |
Yes | Canonical URL of the app (e.g. https://example.com) |
DOMAIN |
Prod only | Public domain — Caddy provisions TLS for this |
CADDY_EMAIL |
Prod only | Email for Let's Encrypt expiry notifications |
POSTGRES_USER |
Prod only | PostgreSQL username for docker-compose |
POSTGRES_PASSWORD |
Prod only | PostgreSQL password |
POSTGRES_DB |
Prod only | Database name |
ADMIN_EMAIL |
Seed only | Initial admin account email |
ADMIN_PASSWORD |
Seed only | Initial admin account password |
# Apply schema changes without generating migration files (dev only)
bun run db:push
# Generate a SQL migration file from schema changes (recommended for production)
bun run db:generate
# Apply pending migrations to the database
bun run db:migrate
# Open Drizzle Studio (visual DB browser at http://localhost:4983)
bun run db:studio
# Seed the initial admin user
bun run db:seed
# Run unit tests (Vitest)
bun run test
# Run unit tests in watch mode
bun run test:watch
# Install Playwright browsers (first time only)
bunx playwright install --with-deps
# Run end-to-end tests (Playwright)
# Automatically builds + previews the app before running
bun run test:e2e
# Run e2e tests with browser visible (headed mode)
bunx playwright test --headed
# Always branch from main
git checkout main
git pull origin main
git checkout -b feat/your-feature-name
# Make changes, then run tests locally before pushing
bun run check # TypeScript + Svelte type check
bun run test # Unit tests
bun run test:e2e # End-to-end tests (optional locally)
# Stage and commit
git add src/ tests/
git commit -m "feat: add your feature description"
# Push and open a PR
git push -u origin feat/your-feature-name
# Open a PR on GitHub → main
# After PR approval and merge, the GitHub Actions deploy workflow
# fires automatically and deploys to production via SSH.
# Clean up local branch
git checkout main
git pull origin main
git branch -d feat/your-feature-name
Run these commands once on a fresh Ubuntu/Debian VPS.
# Generate a dedicated deploy key (ED25519 is fast and secure)
ssh-keygen -t ed25519 -C "github-actions-deploy" -f ~/.ssh/deploy_key
# Print the PUBLIC key — add this to the VPS authorized_keys
cat ~/.ssh/deploy_key.pub
# Print the PRIVATE key — add this as the VPS_SSH_KEY GitHub Secret
cat ~/.ssh/deploy_key
# SSH into your VPS as root (replace with your VPS IP)
ssh root@YOUR_VPS_IP
# Create a deploy user with limited privileges
adduser deploy
usermod -aG sudo deploy
usermod -aG docker deploy # Allow docker commands without sudo (after Docker install)
# Add the deploy SSH key
mkdir -p /home/deploy/.ssh
echo "PASTE_PUBLIC_KEY_HERE" >> /home/deploy/.ssh/authorized_keys
chmod 700 /home/deploy/.ssh
chmod 600 /home/deploy/.ssh/authorized_keys
chown -R deploy:deploy /home/deploy/.ssh
# Disable password authentication (key-only SSH)
sed -i 's/^#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
sed -i 's/^PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
systemctl reload sshd
# Install Docker (official script)
curl -fsSL https://get.docker.com | sh
# Enable Docker on boot
systemctl enable docker
systemctl start docker
# Logout and log back in as deploy user to apply group changes
exit
ssh deploy@YOUR_VPS_IP
# SSH in as the deploy user
ssh deploy@YOUR_VPS_IP
# Create the app directory
sudo mkdir -p /opt/app
sudo chown deploy:deploy /opt/app
# Clone your repository
git clone https://github.com/your-org/svelte-skeleton.git /opt/app
cd /opt/app
# Create and populate the production .env file
cp .env.example .env
nano .env
# Fill in ALL required production values:
# DOMAIN, CADDY_EMAIL, POSTGRES_USER, POSTGRES_PASSWORD,
# POSTGRES_DB, BETTER_AUTH_SECRET, BETTER_AUTH_URL
# First-time build and start
docker compose up -d --build
# Wait for the DB to be ready, then run migrations and seed
sleep 10
docker compose exec app bun run db:migrate
docker compose exec app bun run db:seed
# Verify all services are running
docker compose ps
In your GitHub repository → Settings → Secrets and variables → Actions, add:
| Secret | Value |
|---|---|
VPS_HOST |
Your VPS IP address or domain |
VPS_USER |
deploy |
VPS_SSH_KEY |
Contents of ~/.ssh/deploy_key (the private key) |
VPS_PORT |
22 (optional, only if non-standard) |
From this point, every push to main triggers an automatic deployment.
The colour system is defined in src/app.css using OKLCH. To change the colour palette, edit the CSS custom properties in :root:
:root {
--hue-primary: 272; /* 0–360: violet */
--chroma-primary: 0.20; /* 0–0.4: saturation */
--hue-secondary: 195;
--chroma-secondary: 0.15;
--hue-neutral: 260;
--chroma-neutral: 0.015;
}
The @theme inline block in app.css generates the full Tailwind utility scale (bg-primary-500, text-secondary-200, etc.) from these variables at runtime — meaning dark mode is a pure CSS operation with zero JavaScript re-computation.
# View live logs
docker compose logs -f app
# Restart only the app (e.g. after .env changes)
docker compose restart app
# Pull the latest code and rebuild manually
git pull origin main && docker compose up -d --build
# Run a one-off database migration on the VPS
docker compose exec app bun run db:migrate
# Open a database REPL
docker compose exec db psql -U postgres appdb
# Check Caddy TLS certificate status
docker compose exec caddy caddy list-modules
MIT