Skip to content

03c/jigsaw

Repository files navigation

Jigsaw

A self-hosted web hosting control panel built with React Router 7. Manage websites, databases, and services through a modern web UI -- no cPanel or Plesk licence required.

Each user site runs in isolated Docker containers with its own network, Nginx + PHP-FPM web server, and MariaDB database. Optional per-site SFTP access can be enabled with one click.

Features

  • Site management -- create, start, stop, restart, and delete websites from the browser
  • Isolated containers -- every site gets its own Docker network and web server, with optional database
  • Auto-generated credentials -- database and SFTP passwords are created with cryptographic randomness
  • Automatic SSL -- Traefik provisions and renews Let's Encrypt certificates for every site
  • User authentication -- Keycloak provides login, password reset, MFA, and brute-force protection
  • Role-based access -- admin users can manage all sites and users; regular users see only their own
  • Server dashboard -- real-time CPU, RAM, disk, and network stats for admins
  • Docker admin -- view running containers, prune unused resources, all from the panel
  • SFTP per site -- optional SFTP container with auto-assigned port and generated credentials
  • Per-site home folders -- site content lives under /home/<user>/<site>/public_html
  • Activity log -- track who did what across the panel

Quick Install

Requirements: Ubuntu 22.04+ (or Debian 12+), a domain name with DNS pointed to the server, and ports 80/443 open.

curl -fsSL https://raw.githubusercontent.com/03c/jigsaw/main/install.sh -o /tmp/jigsaw-install.sh && chmod +x /tmp/jigsaw-install.sh && sudo /tmp/jigsaw-install.sh

The installer will:

  1. Install Docker and Docker Compose if not present
  2. Clone the repository to /opt/jigsaw
  3. Ask for your domain, email, and Keycloak admin password
  4. Auto-generate all secrets (database passwords, session key, OIDC client secret), reusing existing .env secrets on reruns
  5. Pull prebuilt panel and PHP images from GHCR
  6. Start the full stack (Traefik, PostgreSQL, Keycloak, Jigsaw panel)
  7. Run database migrations
  8. Validate DNS, request one SAN Let's Encrypt certificate for panel/auth/traefik, then print first-login instructions

If you've already cloned the repo, run the script directly:

sudo ./install.sh

DNS Setup

Point three A records to your server's public IP:

Record Type Value
panel.example.com A <your-server-ip>
auth.panel.example.com A <your-server-ip>
traefik.panel.example.com A <your-server-ip>

Each site you create will also need its own A record pointing to the same IP.

Manual Install

If you prefer to set things up yourself instead of using the installer:

# 1. Clone
git clone https://github.com/03c/jigsaw.git /opt/jigsaw
cd /opt/jigsaw

# 2. Create .env from the example and fill in your values
cp .env.example .env
nano .env

# 3. Generate secrets for .env
#    POSTGRES_PASSWORD:       openssl rand -base64 32 | tr -d '/+='
#    SESSION_SECRET:          openssl rand -hex 32
#    KEYCLOAK_CLIENT_SECRET:  openssl rand -base64 48 | tr -d '/+='

# 4. Patch the Keycloak realm with your client secret and admin email
sed -i "s|JIGSAW_CLIENT_SECRET_PLACEHOLDER|<your-client-secret>|" keycloak/jigsaw-realm.json
sed -i "s|JIGSAW_ADMIN_EMAIL_PLACEHOLDER|<your-email>|" keycloak/jigsaw-realm.json

# 5. Create data directories
mkdir -p data/sites data/databases data/postgres docker/compose

# 6. Pull prebuilt images
docker pull ghcr.io/03c/jigsaw/panel:latest
docker pull ghcr.io/03c/jigsaw/php:8.4
docker tag ghcr.io/03c/jigsaw/php:8.4 jigsaw-php:8.4

# 7. Start the stack
docker compose up -d

# 8. Wait for PostgreSQL, then run migrations
docker compose exec jigsaw npm run db:push

Publish Docker Images

Build and push images from your workstation or CI:

docker build -t ghcr.io/03c/jigsaw/panel:latest .
docker build -t ghcr.io/03c/jigsaw/php:8.4 docker/templates/web/
echo "$GITHUB_TOKEN" | docker login ghcr.io -u <github-username> --password-stdin
docker push ghcr.io/03c/jigsaw/panel:latest
docker push ghcr.io/03c/jigsaw/php:8.4

Post-Install

  1. Open https://panel.example.com -- you'll be redirected to Keycloak
  2. Log in with username admin and the Keycloak admin password you entered during install
  3. You're now in the Jigsaw dashboard as an admin
  4. To create additional users, go to https://auth.panel.example.com and use the Keycloak admin console

Troubleshooting

If Keycloak fails with password authentication failed for user "jigsaw", your PostgreSQL data was initialized with a different password than the one in .env.

For a fresh install, reset PostgreSQL data and rerun:

docker compose down && sudo rm -rf data/postgres && sudo ./install.sh

If Traefik logs client version 1.24 is too old. Minimum supported API version is 1.44, update to the latest repo and recreate containers:

git pull && docker compose down && docker compose up -d

If install times out waiting for SSL certificates, verify panel/auth/traefik domains resolve to this server and that ports 80/443 are reachable from the internet.

If /auth/login returns Unexpected Server Error or Authentication is temporarily unavailable, Keycloak is not ready from the panel container yet. Check:

docker compose logs -f keycloak jigsaw
docker compose exec -T jigsaw node -e "fetch('http://keycloak:8080/realms/jigsaw/.well-known/openid-configuration').then((r)=>r.text().then((t)=>console.log(r.status,t.slice(0,120)))).catch((e)=>{console.error(e);process.exit(1)})"

If Keycloak shows Invalid parameter: redirect_uri, update the jigsaw-panel client redirect URI to exactly:

https://<your-panel-domain>/auth/callback

Then restart the panel:

docker compose restart jigsaw

If the browser shows ERR_TOO_MANY_REDIRECTS after login, clear cookies for server.jigsawhost.com and auth.server.jigsawhost.com, then retry using the exact configured panel domain (not IP or alternate host).

If logs include OAUTH_JSON_ATTRIBUTE_COMPARISON_FAILED with issuer mismatch (expected http://keycloak:8080/... vs issuer https://auth.<domain>/...), pull the latest config and recreate the panel:

git pull && docker compose up -d --force-recreate jigsaw

If certificate names look wrong after changing TLS domain settings, recreate Traefik certificates:

docker compose down
docker volume rm $(docker volume ls -q | grep traefik_letsencrypt)
docker compose up -d

Architecture

Internet
   |
[Traefik]  ports 80/443, auto-SSL
   |
   ├── Jigsaw Panel  (React Router 7, Node.js)
   ├── Keycloak      (authentication)
   ├── PostgreSQL     (shared: panel data + Keycloak data)
   │
   ├── site-a_web    (Nginx + PHP-FPM)   ┐
   ├── site-a_db     (MariaDB)           ├─ isolated network per site
   ├── site-a_sftp   (optional)          ┘
   │
   ├── site-b_web                        ┐
   ├── site-b_db                         ├─ isolated network per site
   └── ...                               ┘

Configuration

All configuration is in the .env file. See .env.example for all available options.

Variable Description
PANEL_DOMAIN Domain for the panel (Keycloak is at auth.<domain>)
ACME_EMAIL Email for Let's Encrypt certificate notifications
POSTGRES_PASSWORD PostgreSQL password (auto-generated)
KEYCLOAK_ADMIN_PASSWORD Keycloak admin console password
KEYCLOAK_CLIENT_SECRET OIDC client secret shared between Keycloak and the panel
KEYCLOAK_CONSOLE_URL URL used for admin Keycloak navigation links
TRAEFIK_DASHBOARD_URL URL used for admin Traefik navigation links
SITE_WEB_IMAGE_TEMPLATE Template used for site web images (default jigsaw-php:{phpVersion})
SITE_DB_IMAGE Database image for new site DB containers (default mariadb:lts)
SITE_SFTP_IMAGE Image for per-site SFTP containers (default atmoz/sftp)
SITES_BASE_PATH_HOST Host base path for site folders (default /home)
SITES_BASE_PATH_PANEL Mounted path inside panel container for writing site files
DOCKER_SOCKET_PATH Docker daemon socket override (useful for Windows local dev)
SESSION_SECRET Encryption key for session cookies

Project Structure

jigsaw/
├── app/
│   ├── components/         # UI components (sidebar, cards, badges)
│   ├── lib/                # Server utilities
│   │   ├── auth.server.ts      # Keycloak OIDC (PKCE flow)
│   │   ├── db.server.ts        # PostgreSQL via Drizzle ORM
│   │   ├── docker.server.ts    # Docker container orchestration
│   │   ├── images.server.ts    # Site image resolution and env overrides
│   │   ├── session.server.ts   # Cookie session + auth guards
│   │   ├── crypto.server.ts    # Password/slug/UUID generation
│   │   └── stats.server.ts     # System & Docker stats
│   ├── models/
│   │   └── schema.ts           # Drizzle schema (users, sites, services, activity_log)
│   ├── routes/                 # React Router 7 routes
│   │   ├── auth.*.tsx          # Login, callback, logout
│   │   ├── dashboard.*.tsx     # User dashboard, site management
│   │   └── admin.*.tsx         # Admin panel, user management, server stats
│   ├── root.tsx
│   └── routes.ts               # Route config
├── docker/
│   ├── templates/web/          # Nginx + PHP-FPM Dockerfile & config
│   ├── templates/site/         # Default site bootstrap templates
│   └── init-keycloak-db.sql    # Creates Keycloak DB in shared PostgreSQL
├── keycloak/
│   └── jigsaw-realm.json       # Keycloak realm with roles, client, default admin
├── docker-compose.yml          # Full stack: Traefik + PostgreSQL + Keycloak + Panel
├── docker-compose.local.yml    # Local-only PostgreSQL + Keycloak services
├── scripts/
│   └── prepare-dev-realm.mjs   # Generates local Keycloak realm import file
├── Dockerfile                  # Multi-stage build for the panel (Node 22)
├── drizzle.config.ts
├── install.sh                  # Interactive installer
├── .env.example
├── package.json
└── tsconfig.json

Tech Stack

Layer Technology
Frontend + Backend React Router 7 (SSR, loaders, actions)
Database (panel) PostgreSQL 17 via Drizzle ORM
Database (sites) MariaDB 11 (one per site)
Auth Keycloak 26 (OIDC/PKCE)
Reverse Proxy Traefik v3 (auto-SSL)
Container Mgmt dockerode (Node.js Docker SDK)
Styling Tailwind CSS 4
Runtime Node.js 22 LTS

Development

For a fast local dev loop (recommended), run the app on your host with HMR and only run backing services in Docker.

Local Dev (Windows-friendly)

npm install

# Create local env file
cp .env.local.example .env.local

# Start local services (PostgreSQL + Keycloak)
npm run dev:services:up

# Push the schema
npm run db:push

# Start the dev server with HMR
npm run dev

PowerShell equivalent for env copy:

Copy-Item .env.local.example .env.local

The app runs at http://localhost:5173 and Keycloak at http://localhost:8080.

Default local credentials:

  • Keycloak admin user: admin
  • Keycloak admin password: admin (change in .env.local)

When you are done:

npm run dev:services:down

To tail local service logs:

npm run dev:services:logs

Optional Full-Stack Parity Locally

If you want to test Traefik + TLS + router labels exactly like production, run the full stack in Docker (docker compose up -d) and point local domains accordingly.

Useful Commands

# View logs
docker compose logs -f
docker compose logs -f jigsaw

# Local dev services
npm run dev:services:up
npm run dev:services:down
npm run dev:services:logs
npm run dev:bootstrap

# Restart the panel after code changes
docker compose restart jigsaw

# Pull and restart the panel
docker compose pull jigsaw && docker compose up -d jigsaw

# Run database migrations
docker compose exec jigsaw npm run db:push

# Open a shell in the panel container
docker compose exec jigsaw sh

# Stop everything
docker compose down

# Stop everything and remove volumes (destructive!)
docker compose down -v

Licence

MIT

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors