Issue #96: an operator fronting the panel with Caddy and a smallstep private CA hit a redirect loop, and there was no documented path to follow. The architecture already tolerated this deployment -- the fix was a config file we already ship -- but nothing said so anywhere. Adds an INSTALLATION.md section covering both topologies (external proxy -> ServerKit nginx, full features, HOPS=2; external proxy -> the app directly, panel-only, HOPS=1), why the loop happens, how to count TRUSTED_PROXY_HOPS, Caddy (with acme_ca for a private CA), Traefik, Nginx Proxy Manager and plain nginx snippets, what a private CA does and does not require (the panel never sees it; a remote agent's host must trust the root), and a symptom-to-cause table for the loop, stale HSTS, the rejected websocket and wrong client IPs. Also corrects the Docker documentation, which had drifted badly: - README (en/es/pt/zh-CN), INSTALLATION and DEPLOYMENT all told people to run `docker compose up -d` and reach the panel at http://localhost. It starts the all-in-one container on :5000, and a containerised panel cannot manage its own host -- now stated up front rather than discovered. - DEPLOYMENT is a host-install guide that told operators to run `docker compose build && up` to fix a panel, which would build a second panel beside theirs, and carried a certbot cron copying certs and restarting a container that does not exist on a host install. - CLAUDE.md, AGENTS.md, CONTRIBUTING.md and LOCAL_DEVELOPMENT.md (x3) pointed at a docker-compose.dev.yml that has never existed; the snippet offering to create it referenced a frontend/Dockerfile.dev that does not exist either. Every anchor introduced here was checked to resolve. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
9.9 KiB
ServerKit Deployment Guide
Complete guide for deploying ServerKit on Ubuntu servers.
Deployment model (important)
ServerKit's backend manages the host it runs on — it creates app
directories under /var/serverkit/apps, drives the host Docker daemon (one
container per managed app), reloads nginx, and runs systemctl/firewall/PHP-FPM.
A backend running inside a container cannot do any of that. So the canonical
deployment installed by ./serverkit install (install.sh) runs fully off
Docker for the panel itself:
- the backend on the host via systemd (
serverkit.service), and - the frontend as static files served directly by host nginx from
frontend/dist(built byinstall.sh) — no container. Host nginx serves the SPA atlocation /and proxies/api+/socket.ioto the host backend on:5000.
Docker is therefore used only for the workloads you host (one container per managed app). This is deliberate: a ServerKit update never touches those containers, and host nginx — which reverse-proxies them — is only ever reloaded, never stopped, so updating the panel causes zero downtime for hosted apps.
Do not add the backend into Docker — a containerized backend fails
app/WordPress creation with Permission denied: /var/serverkit/apps/... and has
no access to the host Docker daemon. A containerized frontend is still
available as an opt-in escape hatch (docker compose --profile legacy-frontend up -d, plus repointing nginx location / at 127.0.0.1:3847) but is not used
by the default install. The single-container image in ./Dockerfile exists only
for throwaway demos and, as its header states, cannot manage the host.
Quick Install (Ubuntu)
# One-line install
curl -fsSL https://serverkit.ai/install.sh | bash
# Or clone and install manually
git clone https://github.com/jhd3197/serverkit.git
cd serverkit
chmod +x serverkit
./serverkit install
Prerequisites
- Ubuntu 20.04+ (or Debian 11+)
- Docker 20.10+ and Docker Compose 2.0+
- At least 1GB RAM
- Domain name (for SSL/HTTPS)
CLI Commands
ServerKit includes a management CLI for common administrative tasks.
Service Management
# Start/Stop/Restart
serverkit start
serverkit stop
serverkit restart
# View status
serverkit status
# View logs
serverkit logs # All services
serverkit logs backend # Backend only
serverkit logs frontend # Frontend only
# Update to latest version
serverkit update
# Uninstall
serverkit uninstall
User Management
# Create admin user
serverkit create-admin
# Prompts for: email, username, password
# Reset a user's password
serverkit reset-password
# Prompts for: email, new password
# Unlock a locked account (after failed login attempts)
serverkit unlock-user
# Prompts for: email
# List all users
serverkit list-users
# Promote user to admin
serverkit make-admin
# Prompts for: email
# Deactivate/Activate user
serverkit deactivate-user
serverkit activate-user
Database Management
# Initialize database
serverkit init-db
# Apply database migrations (after updates)
serverkit migrate-db
# Backup database
serverkit backup-db
# Creates: backup/serverkit-YYYYMMDD-HHMMSS.db
# Restore from backup
serverkit restore-db backup/serverkit-20240115-120000.db
App & Cleanup Commands
# List all installed applications (from database)
serverkit list-apps
# Also show all Docker containers (including infrastructure)
serverkit list-apps --all
# Delete all apps, containers, folders, and orphaned Docker resources
serverkit cleanup-apps
# Also delete Docker volumes
serverkit cleanup-apps --delete-volumes
# Keep database records (only delete containers/folders)
serverkit cleanup-apps --keep-db
# Complete factory reset (delete everything and start fresh)
# Preserves only serverkit-frontend infrastructure
serverkit factory-reset
Note: Cleanup commands will:
- Stop and remove all app containers
- Remove orphaned Docker containers (not tracked in database)
- Prune unused Docker networks
- Delete all folders in
/var/serverkit/apps/ - Never touch ServerKit infrastructure (serverkit-frontend, serverkit-network)
Utility Commands
# Generate secure keys for .env
serverkit generate-keys
# Edit configuration
serverkit config
# Open shell in backend container
serverkit shell
# Show help
serverkit help
Manual Installation
1. Install Docker
# Install Docker
curl -fsSL https://get.docker.com | sh
# Add user to docker group
sudo usermod -aG docker $USER
# Log out and back in, then verify
docker --version
2. Clone Repository
git clone https://github.com/jhd3197/serverkit.git /opt/serverkit
cd /opt/serverkit
3. Configure Environment
# Generate secure keys
serverkit generate-keys
# Create .env file
cat > .env << 'EOF'
SECRET_KEY=<paste-generated-key>
JWT_SECRET_KEY=<paste-generated-key>
DATABASE_URL=sqlite:///serverkit.db
CORS_ORIGINS=https://yourdomain.com
PORT=80
SSL_PORT=443
FLASK_ENV=production
EOF
4. SSL Certificate Setup
Option A: Let's Encrypt (Recommended)
# Install certbot
sudo apt install certbot
# Get certificate (stop any service on port 80 first)
sudo certbot certonly --standalone -d yourdomain.com
# Copy certificates
sudo cp /etc/letsencrypt/live/yourdomain.com/fullchain.pem nginx/ssl/
sudo cp /etc/letsencrypt/live/yourdomain.com/privkey.pem nginx/ssl/
sudo chown -R $USER:$USER nginx/ssl/
Option B: Self-Signed (Development Only)
mkdir -p nginx/ssl
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout nginx/ssl/privkey.pem \
-out nginx/ssl/fullchain.pem \
-subj "/CN=localhost"
5. Build and Start
Both panel tiers run on the host: the backend under systemd
(serverkit.service) and the frontend as static files served by host nginx from
frontend/dist. ./serverkit install builds the bundle and wires nginx for
you. See the Deployment model note above.
# Build the SPA bundle host nginx serves (install.sh does this automatically)
cd frontend && npm ci && npm run build && cd ..
# Start the host backend + nginx
sudo systemctl start serverkit
sudo systemctl reload nginx
# Verify
serverkit status
6. Create Admin User
serverkit create-admin
Systemd Service
Run ServerKit as a system service:
# Copy service file
sudo cp deploy/serverkit.service /etc/systemd/system/
# Enable and start
sudo systemctl daemon-reload
sudo systemctl enable serverkit
sudo systemctl start serverkit
# Check status
sudo systemctl status serverkit
Configuration Reference
Environment Variables
| Variable | Description | Default |
|---|---|---|
SECRET_KEY |
Flask secret key | Required |
JWT_SECRET_KEY |
JWT signing key | Required |
DATABASE_URL |
Database connection string | sqlite:///serverkit.db |
CORS_ORIGINS |
Allowed CORS origins | http://localhost |
PORT |
HTTP port | 80 |
SSL_PORT |
HTTPS port | 443 |
FLASK_ENV |
Environment mode | production |
Host data locations
The backend runs on the host, so its data lives on the host filesystem (not in Docker volumes):
| Path | Purpose |
|---|---|
/opt/serverkit/backend/instance/serverkit.db |
SQLite database (default) |
/var/serverkit/apps/ |
Managed app directories (one per app) |
/etc/serverkit/ |
Config (templates, ssl-mode, install state) |
/var/backups/serverkit/ |
Backups |
Common Tasks
Auto-renew SSL Certificate
Certbot installs its own renewal timer, and host nginx reads the certificates
straight out of /etc/letsencrypt/live/, so there is nothing to copy and no
container to restart. Verify it is armed:
# The renewal timer certbot installed
systemctl list-timers | grep certbot
# Dry-run a renewal without touching the live certificate
sudo certbot renew --dry-run
Only if the timer is missing, add a cron entry that reloads host nginx:
sudo crontab -e
# Renew at 2:30 AM daily, reload nginx only when something actually renewed
30 2 * * * certbot renew --quiet --deploy-hook "systemctl reload nginx"
Change Domain
- Update
.envfile:CORS_ORIGINS=https://newdomain.com - Get new SSL certificate
- Restart:
serverkit restart
Scale for Production
For high-traffic deployments, consider:
# Use PostgreSQL instead of SQLite
DATABASE_URL=postgresql://user:pass@localhost:5432/serverkit
# Add Redis for session storage
# Edit docker-compose.yml to add redis service
Troubleshooting
Panel won't start
This guide covers the host install, where the panel is a systemd service and
not a container — do not reach for docker compose here, it would build a second,
separate all-in-one panel beside this one.
# Check logs
serverkit logs backend
# Restart the service
serverkit restart
serverkit status
# Deeper diagnosis
serverkit doctor
Permission denied errors
# Fix Docker socket permissions
sudo chmod 666 /var/run/docker.sock
# Or add user to docker group
sudo usermod -aG docker $USER
# Then log out and back in
Port already in use
# Find what's using port 80 (host nginx) or 5000 (the panel backend)
sudo lsof -i :80
sudo lsof -i :5000
Reset everything
# Delete all managed apps, containers, folders and orphaned Docker resources
serverkit factory-reset
# Bring the panel back up
serverkit start
serverkit init-db
serverkit create-admin
Locked out / Forgot password
# Reset password via CLI
serverkit reset-password
# Enter email and new password
# Or unlock account if locked
serverkit unlock-user
Security Checklist
- Generated unique SECRET_KEY and JWT_SECRET_KEY
- Using HTTPS with valid SSL certificate
- Firewall configured (allow only ports 80, 443, 22)
- Regular backups configured
- Admin password is strong
- Updated to latest version
Support
- GitHub Issues: https://github.com/jhd3197/serverkit/issues
- Documentation: https://github.com/jhd3197/serverkit/wiki