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:
| Requirement | Details |
|---|---|
| Node.js 22.17+ | LTS recommended. The production server runs as a standard Node.js process. |
| Native module compilation | better-sqlite3 is a C++ addon that compiles during pnpm install. The build
environment needs python3, make, and a C++ compiler. |
| Persistent filesystem | SQLite 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:
| Variable | Required | Default | Description |
|---|---|---|---|
DATABASE_URL | Yes | svelteforge.db | Path to the SQLite database file. In Docker, point to a mounted volume (e.g., /app/data/svelteforge.db). |
ORIGIN | Yes | — | Your production URL (e.g., https://admin.example.com). Required for CSRF
protection and OAuth callback URLs. |
PORT | No | 3000 | Port the Node.js server listens on. |
HOST | No | 0.0.0.0 | Host address to bind to. |
NODE_ENV | No | production | Set automatically by pnpm build. Controls secure cookies and other production
behaviors. |
GOOGLE_CLIENT_ID | No | — | Google OAuth 2.0 client ID. Omit to disable Google login. |
GOOGLE_CLIENT_SECRET | No | — | Google OAuth 2.0 client secret. |
GITHUB_CLIENT_ID | No | — | GitHub OAuth app client ID. Omit to disable GitHub login. |
GITHUB_CLIENT_SECRET | No | — | 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:
| Platform | Why It Fails |
|---|---|
| Cloudflare Pages / Workers | V8 isolates cannot load native C++ Node.js modules. No filesystem access. |
| Vercel Edge Functions | Same V8 isolate limitation. Edge runtime does not support native addons. |
| Vercel Serverless Functions | No persistent filesystem. SQLite database would be lost between invocations. Cold starts add latency. |
| AWS Lambda | Ephemeral filesystem (/tmp only, wiped between invocations). No persistent storage
for SQLite. |
| Netlify Functions | Same 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
- Go to APIs & Services → Credentials in Google Cloud Console
- Edit your OAuth 2.0 Client ID
- Add your production URL to Authorized JavaScript origins (e.g.,
https://admin.example.com) - Add the callback URL to Authorized redirect URIs:
https://admin.example.com/login/google/callback
GitHub Developer Settings
- Go to Settings → Developer settings → OAuth Apps
- Edit your OAuth application
- Set Homepage URL to your production URL
- 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 filesvelteforge.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
rcloneorrsyncto 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:
| Check | Details |
|---|---|
| HTTPS only | Session cookies are set with secure: true in production. Your deployment must use
HTTPS (most hosting providers handle this automatically). |
| ORIGIN env var | SvelteKit uses this for CSRF protection on form actions. Must match your exact production URL. |
| Database file location | Keep the SQLite file outside the public web root. In Docker, use /app/data/ —
never serve it from /app/build/client/. |
| Regular backups | Schedule automated backups of the database file. SQLite corruption is rare but data loss from hardware failure is not. |
| Session table monitoring | Sessions 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 credentials | If you deployed with seeded data, change the default password123 passwords immediately
or re-seed with production credentials. |
| Environment variables | Never 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