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>
7.8 KiB
Local Development Setup for Windows
This guide explains how to run ServerKit locally on Windows for development and testing.
Production Server Specs (Reference)
ServerKit is tested on Digital Ocean with these specs:
| Spec | Value |
|---|---|
| OS | Ubuntu 24.04.3 LTS |
| CPU | 2 cores (AMD) |
| RAM | 4 GB |
| Disk | 25 GB |
| Plan | Basic Droplet (~$12/mo) |
For local development, similar or lower specs work fine.
Prerequisites
- Windows 10/11 with WSL2 enabled
- Docker Desktop for Windows (with WSL2 backend)
- Git for Windows
Option 1: Full Local Development (Recommended)
This mimics the production setup where backend runs on host and frontend in Docker.
Step 1: Install WSL2 + Ubuntu
# Run in PowerShell as Administrator
wsl --install -d Ubuntu-24.04
Restart your computer, then open Ubuntu from Start menu to complete setup.
Step 2: Install Dependencies in WSL
# Update packages
sudo apt update && sudo apt upgrade -y
# Install Python
sudo apt install -y python3 python3-pip python3-venv python3-dev
# Install build tools
sudo apt install -y build-essential libffi-dev libssl-dev
# Install iproute2 (for ss command)
sudo apt install -y iproute2
# Install Docker (inside WSL)
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
# Log out and back in for docker group to take effect
Step 3: Clone and Setup
# Clone the repo (or navigate to your mounted Windows folder)
cd ~
git clone https://github.com/jhd3197/ServerKit.git
cd ServerKit
# Create virtual environment
python3 -m venv venv
source venv/bin/activate
# Install dependencies
pip install -r backend/requirements.txt
pip install gunicorn gevent gevent-websocket
# Create .env file
cat > .env << 'EOF'
SECRET_KEY=dev-secret-key-change-in-production
JWT_SECRET_KEY=dev-jwt-key-change-in-production
DATABASE_URL=sqlite:///backend/instance/serverkit.db
CORS_ORIGINS=http://localhost:41921,http://127.0.0.1:41921,http://localhost:47927,http://127.0.0.1:47927
FLASK_ENV=development
PORT=47927
EOF
# Create instance directory
mkdir -p backend/instance
Step 4: Run Backend (Terminal 1)
cd ~/ServerKit
source venv/bin/activate
cd backend
export $(cat ../.env | xargs)
python run.py
Backend will be available at: http://localhost:47927
Step 5: Run Frontend (Terminal 2)
cd ~/ServerKit/frontend
npm install
npm run dev -- --host 127.0.0.1 --port 41921 --strictPort
Frontend will be available at: http://localhost:41921
Option 2: Docker Compose (Simpler, but no host system access)
Use this for quick UI testing. Note: System management features (PHP install, firewall, etc.) won't work.
The repository's docker-compose.yml already does this — it builds the
all-in-one image from Dockerfile (gunicorn serving the API, the SPA and
websockets together) and publishes it on port 5000.
cp .env.example .env # set SECRET_KEY / JWT_SECRET_KEY
docker compose up -d --build
Then open http://localhost:5000. Override the published port with
SERVERKIT_HTTP_PORT in .env if 5000 is taken.
docker compose logs -f # follow the panel log
docker compose down # stop, keep the database volume
docker compose down -v # stop and destroy the database
There is no hot reload in this mode — the SPA is a production build baked into
the image, so a frontend change needs docker compose up -d --build. For an
edit-refresh loop, use Option 1 (Vite HMR + Flask reloader).
Option 3: VS Code Dev Container
Add this to .devcontainer/devcontainer.json:
{
"name": "ServerKit Dev",
"dockerComposeFile": "../docker-compose.yml",
"service": "serverkit",
"workspaceFolder": "/app",
"customizations": {
"vscode": {
"extensions": [
"ms-python.python",
"ms-python.vscode-pylance",
"dbaeumer.vscode-eslint"
]
}
},
"forwardPorts": [5000],
"postCreateCommand": "pip install -r backend/requirements.txt"
}
Quick Reference
| Component | Production (DO Server) | Local Dev (WSL) |
|---|---|---|
| Backend | systemd service | python run.py |
| Frontend | Docker + nginx | npm run dev |
| Database | SQLite in /opt/serverkit | SQLite in ./backend/instance |
| System Access | Full (sudo, systemctl) | Full (in WSL) |
Testing System Features
To test PHP installation, firewall, etc. locally:
- Use Option 1 (WSL) - this gives you a real Linux environment
- Install the same packages as production:
sudo apt install -y firewalld vsftpd - The backend will have real system access
Environment Variables
| Variable | Description | Default |
|---|---|---|
SECRET_KEY |
Flask secret key | Required |
JWT_SECRET_KEY |
JWT signing key | Required |
DATABASE_URL |
SQLite path | sqlite:///serverkit.db |
CORS_ORIGINS |
Allowed origins | http://localhost |
FLASK_ENV |
development/production | production |
Troubleshooting
Port already in use
# Find what's using the port
sudo lsof -i :47927
# Kill it
sudo kill -9 <PID>
Permission denied for Docker
sudo usermod -aG docker $USER
# Log out and back in
Database locked
# Remove the lock file
rm backend/instance/serverkit.db-journal
Exposing Your Local Control Plane (ngrok)
When developing locally on Windows/WSL you may want a remote ServerKit agent (or
a Docker host on another machine) to connect back to your machine. The dev.sh
and dev.ps1 scripts include a tunnel mode that starts the backend, frontend,
and an ngrok tunnel pointed at the active backend port printed by the launcher.
Prerequisites
-
Install ngrok and authenticate once:
# macOS / Linux / WSL brew install ngrok/ngrok/ngrok # or: snap install ngrok ngrok config add-authtoken <YOUR_TOKEN># Windows choco install ngrok # or: scoop install ngrok ngrok config add-authtoken <YOUR_TOKEN> -
(Optional) Reserve a domain in the ngrok dashboard so the URL stays stable across restarts.
Start with a tunnel
# Linux / macOS / WSL
./dev.sh tunnel
# Windows
.\dev.ps1 tunnel
The public ngrok URL is printed in green once the tunnel is up. Use it as the
--server argument when registering an agent:
serverkit-agent register \
--token "sk_reg_xxx" \
--server "https://<your-tunnel>.ngrok-free.app"
Optional environment variables
| Variable | Purpose |
|---|---|
NGROK_DOMAIN |
Use a reserved ngrok domain (e.g. my-app.ngrok-free.app). |
NGROK_AUTHTOKEN |
Override the authtoken (otherwise the one stored by ngrok config is used). |
CORS_ORIGINS |
Override CORS allow-list. The script defaults include https://*.ngrok-free.app, https://*.ngrok.app, https://*.ngrok.io. |
Quick Start with WSL2 (Helper Script)
If you're on Windows and using WSL2 Ubuntu/Debian, you can skip the manual steps above and use the bundled setup helper:
# from inside WSL, in the cloned repo
bash scripts/setup-wsl.sh # full setup
bash scripts/setup-wsl.sh --check # dry-run / diagnostics only
The script will:
- Detect WSL and warn if the repo lives under
/mnt/c(slow file I/O for hot-reload). - Verify Ubuntu/Debian and install missing apt packages
(
python3,python3-venv,python3-pip,nodejs,npm,git,curl). - Create
backend/venvand install Python requirements. - Run
npm installinfrontend/. - Copy
backend/.env.exampletobackend/.envif missing. - Print WSL-specific tips (localhost forwarding, polling reloader, ngrok).
Once it finishes:
./dev.sh # start backend + frontend
./dev.sh tunnel # also expose backend via ngrok (see section above)