Self-host your SvelteKit frontend on a VPS using Dokploy. Complete guide to Docker deployment with SSL and automated deployments.
Deploying SvelteKit to VPS with Dokploy
Dokploy is an open-source Platform as a Service (PaaS) that simplifies deploying applications on your own VPS. This guide covers deploying SvelteKit to a VPS using Dokploy for complete control over your infrastructure.
Why Self-Host?
- Cost: Fixed monthly cost vs per-usage pricing
- Control: Full control over server configuration
- Privacy: Data stays on your infrastructure
- Learning: Understand deployment fundamentals
- No vendor lock-in: Move providers easily
Prerequisites
- VPS with Docker support (Ubuntu 20.04+ recommended)
- Domain name pointing to your VPS
- SvelteKit application ready for deployment
- SSH access to your server
Install Development Tools Locally
We recommend using mise (version manager) and pnpm (package manager):
Install mise:
curl https://mise.run | sh
echo 'eval "$(mise activate)"' >> ~/.bashrc
source ~/.bashrcInstall Node.js and pnpm:
mise use -g nodejs@latest
mise x nodejs@latest -- npm install -g pnpmWhy pnpm over npm?
- Faster installation through content-addressable storage
- Shared package store across projects saves disk space
- Strict dependency isolation prevents phantom dependencies
- More reliable lockfile ensures reproducible builds
Step 1: Set Up Your VPS
Server Requirements
- CPU: 1+ cores
- RAM: 1GB+ (2GB recommended)
- Storage: 20GB+ SSD
- OS: Ubuntu 20.04/22.04 LTS
Initial Server Setup
Connect to your server:
ssh root@your-server-ipUpdate system:
apt update && apt upgrade -yCreate a non-root user:
adduser deploy
usermod -aG sudo deployConfigure SSH (optional but recommended):
# Edit SSH config
nano /etc/ssh/sshd_config
# Disable root login
PermitRootLogin no
# Disable password auth (only if using keys)
PasswordAuthentication no
# Restart SSH
systemctl restart sshdInstall Docker
# Install Docker
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh
# Add user to docker group
usermod -aG docker deploy
# Verify
systemctl status dockerStep 2: Install Dokploy
# As root or with sudo
curl -sSL https://dokploy.com/install.sh | bashThis installs:
- Dokploy management interface
- Traefik reverse proxy
- Docker networking
Access Dokploy
- Open firewall port (if using UFW):
ufw allow 3000/tcp- Access Dokploy dashboard:
http://your-server-ip:3000- Complete initial setup (create admin account)
Step 3: Prepare Your SvelteKit App
Docker Configuration
Create frontend/Dockerfile:
# Build stage
FROM node:20-alpine AS builder
WORKDIR /app
# Copy package files
COPY package*.json ./
COPY pnpm-lock.yaml* ./
COPY yarn.lock* ./
# Install dependencies
RUN if [ -f pnpm-lock.yaml ]; then npm install -g pnpm && pnpm install --frozen-lockfile; \
elif [ -f yarn.lock ]; then yarn install --frozen-lockfile; \
else npm ci; fi
# Copy source
COPY . .
# Build
ENV NODE_ENV=production
RUN if [ -f pnpm-lock.yaml ]; then pnpm run build; \
elif [ -f yarn.lock ]; then yarn build; \
else npm run build; fi
# Production stage
FROM node:20-alpine
WORKDIR /app
# Copy built app
COPY --from=builder /app/build ./build
COPY --from=builder /app/package*.json ./
COPY --from=builder /app/node_modules ./node_modules
# Expose port
EXPOSE 3000
# Health check
HEALTHCHECK --interval=30s --timeout=3s \
CMD wget -qO- http://localhost:3000/health || exit 1
# Start
CMD ["node", "build"]Create frontend/.dockerignore:
node_modules
.pnpm-debug.log
Dockerfile
.dockerignore
.git
.gitignore
README.md
.env
.env.*
!.env.productionUpdate Adapter
Ensure svelte.config.js uses the Node adapter:
import adapter from '@sveltejs/adapter-node';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
const config = {
preprocess: vitePreprocess(),
kit: {
adapter: adapter({
out: 'build'
})
}
};
export default config;Install adapter:
pnpm install -D @sveltejs/adapter-nodeEnvironment Variables
Create frontend/.env.production (committed to repo):
PUBLIC_API_URL=https://tcs-svc-9k2m.yourdomain.com
PUBLIC_GRAPHQL_ENDPOINT=https://tcs-svc-9k2m.yourdomain.com/graphqlSecurity Note: We use an obscure subdomain (tcs-svc-9k2m) instead of predictable names like api or backend. Attackers constantly scan for common subdomains. An unguessable name hides your API from automated reconnaissance and reduces drive-by attacks. Choose your own unique pattern and keep it private.
Step 4: Configure Dokploy
Add Server to Dokploy
- Log into Dokploy dashboard
- Go to "Servers"
- Click "Add Server"
- Enter your server details
Create Application
- Go to "Applications"
- Click "Create Application"
- Configure:
General:
- Name:
frontend - Description:
SvelteKit frontend
Source:
- Git Provider: GitHub/GitLab
- Repository: yourusername/your-repo
- Branch: main
- Build Path:
frontend
Build:
- Build Type: Dockerfile
- Dockerfile Path:
Dockerfile
Environment:
- Port: 3000
Environment Variables
Add in Dokploy UI:
| Variable | Value |
|---|---|
NODE_ENV |
production |
PUBLIC_API_URL |
https://tcs-svc-9k2m.yourdomain.com |
Domain Configuration
- In application settings, go to "Domains"
- Add domain:
tcs-app-7x4p.yourdomain.com - Enable HTTPS (Traefik handles SSL)
Step 5: Deploy
Trigger Deployment
- Click "Deploy" in Dokploy UI
- Or push to main branch (auto-deploy if configured)
Monitor Deployment
View logs in real-time:
- Build logs
- Container logs
- Error logs
Verify Deployment
# Check container is running
docker ps
# Check logs
docker logs frontend
# Test locally on server
curl http://localhost:3000Step 6: Configure DNS
Point Domain to Server
At your DNS provider:
Type: A
Name: app
Value: your-server-ip
TTL: 3600Or for apex domain:
Type: A
Name: @
Value: your-server-ipSSL Certificate
Dokploy with Traefik automatically handles SSL via Let's Encrypt.
Verify HTTPS works:
curl -I https://tcs-app-7x4p.yourdomain.comStep 7: Continuous Deployment
Webhook Setup
In Dokploy, get webhook URL:
- Application → Settings → Webhooks
Add to GitHub/GitLab:
- Repository → Settings → Webhooks
- Payload URL: Dokploy webhook URL
- Content type:
application/json - Events: Push
Auto-Deploy Configuration
In Dokploy application settings:
- Enable "Auto Deploy"
- Select branches to watch
Advanced Configuration
Multiple Instances
Scale horizontally in Dokploy:
- Application → Settings → Replicas
- Set to desired count (e.g., 2)
Health Checks
Add to Dockerfile:
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/ || exit 1Environment-Specific Builds
Create separate Dokploy applications:
frontend-productionfrontend-staging
Each with different:
- Git branches
- Environment variables
- Domains
Backup Strategy
SvelteKit frontend is stateless (just files), but backup your:
- Dokploy configuration
- Environment variable documentation
- SSL certificates (Traefik handles automatically)
Troubleshooting
Container Won't Start
Check logs:
docker logs frontend
docker logs traefikCommon issues:
- Missing environment variables
- Port already in use
- Build failure
502 Bad Gateway
Usually means:
- Container not running
- Wrong port exposed
- Health check failing
Check:
docker ps # Verify container is up
docker inspect frontend # Check configurationSSL Not Working
Verify:
- Domain DNS points to server
- Traefik is running:
docker logs traefik - No firewall blocking:
ufw status
Build Fails
Test locally first:
docker build -t test-build .Production Checklist
- VPS secured (SSH keys, firewall)
- Dokploy installed and configured
- Application builds successfully
- Domain DNS configured
- HTTPS working with valid certificate
- Environment variables set
- Health checks configured
- Monitoring/logging configured
- Backup strategy documented
- Auto-deploy configured (optional)
Cost Comparison
| Platform | Monthly Cost | Notes |
|---|---|---|
| Cloudflare Pages Pro | $5 | Generous free tier, Pro adds more builds |
| Netlify Pro | $19+ | Bandage limits |
| VPS (Hetzner) | €5 | 2GB RAM, 20GB |
| VPS (DigitalOcean) | $6 | 1GB RAM, 25GB |
| VPS (Linode) | $5 | 1GB RAM, 25GB |
VPS costs are fixed regardless of traffic (within resource limits).