Deployment

SvelteForge Admin is a Svelte 5 and SvelteKit application that uses @sveltejs/adapter-node for Node.js deployment. Because the database is SQLite (via better-sqlite3), your hosting environment must provide a persistent filesystem and a full Node.js runtime.

Requirements

Before deploying your SvelteKit application, make sure your target environment meets these requirements:

RequirementDetails
Node.js 22.17+LTS recommended. The production server runs as a standard Node.js process.
Native module compilationbetter-sqlite3 is a C++ addon that compiles during pnpm install. The build environment needs python3, make, and a C++ compiler.
Persistent filesystemSQLite stores data in a single file on disk. The filesystem must survive restarts, deploys, and container recreations.

Why These Matter

SQLite is an embedded database — it runs inside your Node.js process, not as a separate service. The better-sqlite3 driver is a native C++ addon that binds directly to the SQLite C library. This means:

  • No V8 isolates — Native C++ addons cannot run in V8 isolate environments like Cloudflare Workers or Vercel Edge Functions.
  • No ephemeral filesystems — If the filesystem resets between requests (as in serverless functions), your database is gone.
  • No cold start compilation — The native module must be pre-compiled for the target OS/architecture during the build step.

Building for Production

When configuration lives in .env, load it explicitly with Node’s --env-file option. The Node adapter does not load dotenv files automatically. Hosting platforms can supply environment variables directly instead.

SvelteKit compiles your Svelte 5 components, server routes, and hooks into an optimized production bundle:

# Create the production build
pnpm build

# Run the production server
node --env-file=.env build/index.js

The pnpm build command creates a build/ directory containing the compiled SvelteKit application. The adapter-node configuration in vite.config.ts handles the output format:

// vite.config.ts
import adapter from "@sveltejs/adapter-node";
import { sveltekit } from "@sveltejs/kit/vite";
import { defineConfig, loadEnv } from "vite";

export default defineConfig(({ mode }) => {
  const env = loadEnv(mode, process.cwd(), "");
  return {
    plugins: [sveltekit({
      adapter: adapter({ out: "build" }),
      paths: { origin: env.ORIGIN || undefined },
    })],
  };
});

The production server listens on 0.0.0.0:3000 by default. Override with HOST and PORT environment variables.

Environment Variables

Configure your production SvelteKit deployment with these environment variables:

VariableRequiredDefaultDescription
DATABASE_URLYessvelteforge.dbPath to the SQLite database file. In Docker, point to a mounted volume (e.g., /app/data/svelteforge.db).
ORIGINYes—Your production URL (e.g., https://admin.example.com). Required for CSRF protection and OAuth callback URLs.
PORTNo3000Port the Node.js server listens on.
HOSTNo0.0.0.0Host address to bind to.
NODE_ENVNoproductionSet automatically by pnpm build. Controls secure cookies and other production behaviors.
GOOGLE_CLIENT_IDNo—Google OAuth 2.0 client ID. Omit to disable Google login.
GOOGLE_CLIENT_SECRETNo—Google OAuth 2.0 client secret.
GITHUB_CLIENT_IDNo—GitHub OAuth app client ID. Omit to disable GitHub login.
GITHUB_CLIENT_SECRETNo—GitHub OAuth app client secret.

OAuth providers are loaded dynamically in #lib/server/oauth.ts using SvelteKit's $app/env/private. When environment variables are missing, the provider is null and the corresponding social login button is automatically hidden from the login page.

Docker Deployment

Docker is the recommended way to deploy SvelteForge Admin. The multi-stage build keeps the final image small. It uses the Node 24 runtime and the pinned pnpm version.

Dockerfile

FROM node:24-slim AS builder

RUN corepack enable && corepack prepare [email protected] --activate
WORKDIR /app

COPY . .
RUN pnpm install --frozen-lockfile
ARG ORIGIN=http://localhost:3000
ENV ORIGIN=$ORIGIN
RUN pnpm build

FROM node:24-slim AS runner

RUN corepack enable && corepack prepare [email protected] --activate
WORKDIR /app

COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
RUN pnpm install --frozen-lockfile --prod --ignore-scripts
RUN pnpm rebuild better-sqlite3

COPY --from=builder /app/build ./build
COPY drizzle ./drizzle

ENV NODE_ENV=production
ENV PORT=3000
ENV DATABASE_URL=/app/data/svelteforge.db
RUN mkdir -p /app/data

EXPOSE 3000

CMD ["node", "build/index.js"]

docker-compose.yml

version: "3.8"
services:
  svelteforge:
    build:
      context: .
      args:
        ORIGIN: https://admin.example.com
    ports:
      - "3000:3000"
    volumes:
      - ./data:/app/data
    environment:
      - DATABASE_URL=/app/data/svelteforge.db
      - ORIGIN=https://admin.example.com # Runtime OAuth callback URL
      - GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID}
      - GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET}
      - GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID}
      - GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET}
    restart: unless-stopped

Build and Run

# Build the Docker image
docker build -t svelteforge-admin .

# Run with a persistent volume for SQLite
docker run -p 3000:3000 -v ./data:/app/data svelteforge-admin

Important: The -v ./data:/app/data volume mount is critical. Without it, your SQLite database lives inside the container's ephemeral filesystem and will be lost when the container is recreated or updated.

Recommended Hosting Providers

These providers support Node.js applications with persistent filesystems — exactly what a SvelteKit + SQLite application needs:

Railway

The easiest option for deploying SvelteKit applications. Railway detects your Node.js project automatically, runs pnpm build, and starts the server.

  • One-click deploy from GitHub repository
  • Persistent volumes for SQLite (add via dashboard)
  • Free tier with $5/month of usage included
  • Set environment variables in the dashboard or via CLI
  • Automatic HTTPS with custom domains
# Railway CLI deployment
railway init
railway volume add --mount /app/data
railway up

Fly.io

Global edge deployment with persistent volumes. Fly.io runs your SvelteKit application in lightweight VMs close to your users.

  • Deploy via Dockerfile (uses the Dockerfile above)
  • Persistent volumes with fly volumes create
  • Free tier includes 3 shared VMs
  • Global distribution with automatic TLS
# Fly.io deployment
fly launch
fly volumes create svelteforge_data --size 1 --region ord
fly deploy

Render

Auto-deploy from GitHub with zero configuration. Render builds and deploys your SvelteKit application on every push.

  • Connect your GitHub repository for automatic deploys
  • Persistent disks available on paid plans
  • Free tier for web services (with sleep after inactivity)
  • Built-in environment variable management

VPS (DigitalOcean, Hetzner, Linode)

Full control over your deployment. Ideal for teams that want to manage their own infrastructure. VPS providers offer the most flexibility and best price-performance ratio.

  • Starting from ~$4/month (Hetzner) or ~$6/month (DigitalOcean, Linode)
  • Full root access — install Node.js, configure nginx, set up SSL
  • Use PM2 or systemd to keep the Node.js process running
  • SQLite persistence is automatic (it is just a file on the server)
# Example: PM2 process manager on a VPS
npm install -g pm2
pm2 start build/index.js --name svelteforge --node-args="--env-file=.env"
pm2 save
pm2 startup

NOT Compatible

These platforms cannot run SvelteForge Admin due to better-sqlite3 being a native C++ addon that requires a full Node.js runtime and persistent filesystem:

PlatformWhy It Fails
Cloudflare Pages / WorkersV8 isolates cannot load native C++ Node.js modules. No filesystem access.
Vercel Edge FunctionsSame V8 isolate limitation. Edge runtime does not support native addons.
Vercel Serverless FunctionsNo persistent filesystem. SQLite database would be lost between invocations. Cold starts add latency.
AWS LambdaEphemeral filesystem (/tmp only, wiped between invocations). No persistent storage for SQLite.
Netlify FunctionsSame serverless limitations — no persistent filesystem, cold start overhead.

The core issue: better-sqlite3 is a synchronous, native C++ addon that compiles against the Node.js N-API. It requires dlopen() to load the shared library at runtime — something V8 isolates and edge runtimes simply do not support. Additionally, SQLite needs a persistent filesystem to store its database file, WAL journal, and shared-memory file.

Production OAuth Setup

When moving from development to production, update your OAuth provider configurations to use your production URL:

Google Cloud Console

  1. Go to APIs & Services → Credentials in Google Cloud Console
  2. Edit your OAuth 2.0 Client ID
  3. Add your production URL to Authorized JavaScript origins (e.g., https://admin.example.com)
  4. Add the callback URL to Authorized redirect URIs: https://admin.example.com/login/google/callback

GitHub Developer Settings

  1. Go to Settings → Developer settings → OAuth Apps
  2. Edit your OAuth application
  3. Set Homepage URL to your production URL
  4. Set Authorization callback URL to: https://admin.example.com/login/github/callback

Important: Set the ORIGIN environment variable before building (rebuild when it changes) to match your deployed URL exactly (including the protocol, no trailing slash). SvelteKit uses this for CSRF protection — if it does not match, form submissions will fail with a 403 error.

Database Backup

SQLite makes backups straightforward — the database is a single file. However, because SvelteForge Admin runs SQLite in WAL (Write-Ahead Logging) mode, there are a few nuances.

WAL Mode Files

When WAL mode is active, SQLite uses up to three files:

  • svelteforge.db — the main database file
  • svelteforge.db-wal — the write-ahead log (uncommitted changes)
  • svelteforge.db-shm — shared memory index for the WAL

For a consistent backup, copy all three files together, or use SQLite's .backup command which creates a clean, self-contained copy:

# Method 1: SQLite backup command (recommended — creates a clean copy)
sqlite3 svelteforge.db ".backup /backups/svelteforge-$(date +%Y%m%d).db"

# Method 2: Copy all WAL files together
cp svelteforge.db svelteforge.db-wal svelteforge.db-shm /backups/

Automated Backup Strategies

  • Cron job — Schedule a daily backup with crontab -e:
    # Daily backup at 3 AM
    0 3 * * * sqlite3 /app/data/svelteforge.db ".backup /backups/svelteforge-$(date +\%Y\%m\%d).db"
  • Volume snapshots — If using Railway, Fly.io, or a cloud VPS, take periodic snapshots of the volume containing the database.
  • Off-site sync — Use rclone or rsync to copy backups to cloud storage (S3, R2, etc.) for disaster recovery.

Performance Considerations

SQLite with better-sqlite3 is exceptionally fast for admin dashboard workloads. Here is what you need to know for production:

  • WAL mode enables concurrent reads — Multiple SvelteKit server load functions can query the database simultaneously without blocking each other. This is enabled by default in SvelteForge Admin.
  • Single-writer limitation — Only one write transaction can execute at a time. This is fine for admin dashboards where write operations are infrequent (user updates, page edits, setting changes). SQLite handles thousands of writes per second on modern hardware.
  • No connection pooling needed — better-sqlite3 is synchronous and runs in the same process as your SvelteKit server. There is no network overhead, no connection handshake, and no pool to manage.
  • Synchronous by design — better-sqlite3 queries are synchronous, which means they block the Node.js event loop. For the small result sets typical of admin dashboards (tens to hundreds of rows), this is imperceptible. If you ever need to handle thousands of concurrent users, consider switching to PostgreSQL with @sveltejs/adapter-node.

Security Checklist

Before going live with your SvelteKit production deployment, verify these security measures:

CheckDetails
HTTPS onlySession cookies are set with secure: true in production. Your deployment must use HTTPS (most hosting providers handle this automatically).
ORIGIN env varSvelteKit uses this for CSRF protection on form actions. Must match your exact production URL.
Database file locationKeep the SQLite file outside the public web root. In Docker, use /app/data/ — never serve it from /app/build/client/.
Regular backupsSchedule automated backups of the database file. SQLite corruption is rare but data loss from hardware failure is not.
Session table monitoringSessions are auto-extended but never auto-deleted for inactive users. Monitor the sessions table size and consider a periodic cleanup job for expired sessions.
Change default credentialsIf you deployed with seeded data, change the default password123 passwords immediately or re-seed with production credentials.
Environment variablesNever commit .env files to source control. Use your hosting provider's secrets management.

Next Steps

  • Authentication — Deep dive into the session-based auth system and OAuth configuration
  • Database — Full schema reference, migrations, and query patterns
  • API Reference — Server-side utilities, auth functions, and API endpoints

Need More?

Production-Ready with DashboardPack

Choose a template that matches your backend and deployment needs. SvelteForge Premium includes workspace-scoped SaaS modules on SQLite; Apex Svelte provides a prerendered interface ready for your API integration.

  • Apex (Svelte) — static-deploy ready for any host, at the root or a subpath
  • Zenith — Horizontally scalable architecture with database connection pooling
  • Signal — Infrastructure monitoring with health checks, uptime tracking, and alerting