Installation
Install Squilla via Docker or from source.
Prerequisites
- Docker 24+ with Compose v2. Compose v1 (the standalone
docker-composebinary) is not supported — the bundled compose files use thecondition: service_healthydependency form which is v2-only. - PostgreSQL 16 — used as a real dependency, not a swappable target. Squilla relies on JSONB, GIN indexes, and PostgreSQL-specific upsert syntax. The bundled compose service runs
postgres:16-alpine; if you wire your own database, it must be 16 or newer. - Optional but recommended: an MCP-aware AI client (Claude Desktop, Cursor, the Anthropic
mcpCLI, or any client that speaks the Model Context Protocol). Squilla is fully usable without one — the admin SPA does everything — but the design target is AI-driven authoring.
Quickstart with Docker (development)
The fastest path to a running Squilla is the bundled docker-compose.yml at the repo root. It boots PostgreSQL 16, the Squilla core, and the ten reference extensions (content-blocks, email-manager, forms, hello-extension, media-manager, resend-provider, seo-extension, sitemap-generator, smtp-provider, visual-editor) into a single network with two persistent volumes.
git clone https://github.com/erikkubica/squilla.git
cd squilla
docker compose up --build
# open http://localhost:8099
# the dev compose file pre-seeds admin@squilla.local / admin123
# to force a generated random password instead, unset ADMIN_PASSWORD
# in docker-compose.yml before the first boot
The dev compose file bakes ADMIN_EMAIL=admin@squilla.local and ADMIN_PASSWORD=admin123 into the environment so smoke tests and local logins keep working between rebuilds. Those are the defaults you log in with on first boot. They are not safe for production — see the Coolify section below for the one-click production path that generates strong secrets automatically.
What the volumes do
The dev compose file mounts two host paths into the container:
./storage→/app/storage— default backing store for the local file driver (uploaded media bytes when no S3-style provider extension is active)../data→/app/data— persistent operator data. Themes installed viacore.theme.deployor unzipped manually land in/app/data/themes/<slug>/; operator-installed extensions go to/app/data/extensions/<slug>/. Image-bundled themes and extensions stay in/app/themes/and/app/extensions/(read-only intent); the scanner reads both roots and merges them.
This split matters if you nuke the container: image-bundled assets will come back from the rebuild, but anything you installed at runtime via MCP only survives because ./data is on the host.
docker compose logs app. Lose it and you lose passwordless recovery — but you are not locked out: the binary ships a squilla reset-password <email> <new-password> sub-command that runs against the database without needing a server (docker compose exec app squilla reset-password admin@squilla.local newpass).Production with Coolify
The repo also ships coolify-compose.yml, a parallel compose file tuned for one-click Coolify deploys. The differences from the dev file:
- Image is pulled from
ghcr.io/erikkubica/squilla:latestinstead of being built locally. - All secrets — database password, session secret, the AES-256 master key used for encrypted settings, the monitoring bearer token — are generated by Coolify's
SERVICE_*magic variables on first deploy. You do not paste anything; you click Deploy. - No
ADMIN_PASSWORDis set, so the kernel generates a random one and prints it to theappcontainer's logs exactly once. Copy it, log in, change it. CORS_ORIGINSis bound toSERVICE_URL_APP(the public origin Coolify computes from your bound domain), not the auto-generated internal subdomain thatSERVICE_FQDN_APP_8099would give you — that's intentional, the subdomain form is wrong for browser CORS.
If you need a different orchestrator (Nomad, Kubernetes, plain systemd), use coolify-compose.yml as the reference for which env vars matter and which volumes to persist. The CLAUDE.md file in the repo root is the authoritative list of conventions.
Build from source
If you want to hack on the kernel, an extension plugin, or the admin UI itself:
git clone https://github.com/erikkubica/squilla.git
cd squilla
make deploy-local
# this single target:
# 1. builds admin-ui/ (Vite, with the icon shim regenerated)
# 2. builds every extensions/*/admin-ui (in series, one Vite per extension)
# 3. builds every extensions/*/editor-ui (public-site bundles like the visual editor's runtime)
# 4. cross-compiles the squilla binary for linux/arm64
# 5. cross-compiles every extension plugin (extensions/*/cmd/plugin) for linux/arm64
# 6. docker build -f Dockerfile.local -t squilla:local .
# 7. docker compose up -d --no-build
The default cross-compile target is linux/arm64 because the maintainer's deploy environment is arm64. If you're on amd64 hardware, override GOARCH in your environment or edit the Makefile — the binary will not run otherwise.
Hot-deploying just the theme or a single extension
Full make deploy-local rebuilds are slow. While iterating, you can skip the binary and image rebuild entirely. The repo ships make theme for themes (defaults to THEME=squilla; override with make theme THEME=hello-vietnam) which copies the theme directory into the running container and restarts the app. Extension authors can do the same with a docker compose cp of just extensions/<slug>/ followed by an MCP call to core.extension.rescan — the kernel's filesystem watcher picks new directories up automatically, but rescan is the explicit, idempotent trigger you want from CI scripts.
Verifying the install
A healthy Squilla responds at GET /admin/api/boot with a 200 and a small JSON manifest containing the authenticated user, the active extensions and their entry bundles, the navigation tree, and the registered node types. That endpoint is the source of truth for what the admin SPA can see.
# Health check (admin-authed):
curl -i http://localhost:8099/admin/api/boot \
-b 'session=<copy from your browser>'
# expected: HTTP/1.1 200 OK with a JSON BootManifest
# Public homepage:
curl -i http://localhost:8099/
# on a fresh install with no THEME_PATH env and no active_theme
# setting in the database, the kernel falls back to themes/default
# (cmd/squilla/main.go:258). Activate a different theme to swap the look.
# MCP smoke test (token-authed):
curl -X POST http://localhost:8099/mcp \
-H 'Authorization: Bearer <token from /admin/security/mcp-tokens>' \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
docker compose logs -f app in another terminal during your first session. Tengo seed errors, plugin startup messages, theme-activation logs, and the one-time admin password (when generated) only show there. Plugins are out-of-process by design — a misbehaving extension does not bring the kernel down, but it does scroll past in the same log stream.