Architecture
What it is
Section titled “What it is”Gisila Panel is a control plane for one Linux host. It has three programs of its own, plus two external services it depends on:
| Piece | Runs as | Job |
|---|---|---|
gisila-panel (API server) |
unprivileged gisila user |
HTTP API, WebSockets, the bundled web UI, the MCP endpoint. Records intent in Postgres and queues jobs in Redis. |
gisila-worker |
root |
Pops jobs off Redis queues and runs them by calling the agent. Also runs the cron, metrics, health, backup and alert loops. |
gisila-agent |
root (invoked by the worker) |
The only code that touches the host: Linux users, builds, systemd units, Nginx vhosts, AppArmor, certbot, ufw. |
| PostgreSQL 16+ | external | Panel state: users, teams, apps, deployments, encrypted secrets. |
| Redis | external | Job queues, log pub/sub, rate limits, health snapshots. |
Postgres and Redis are dependencies, not components. The installer doesn’t install or manage them. They can run on the same host, another machine or a managed provider.
When to use it
Section titled “When to use it”Read this page when you need to:
- Debug why a change in the UI hasn’t reached the host yet (it’s queued, and the worker hasn’t run it).
- Decide where to put Postgres and Redis.
- Understand what an app’s systemd unit and Nginx vhost look like.
How it works
Section titled “How it works” Browser / CLI / AI assistant │ HTTPS (JWT or gsl_ API token) ▼ Nginx (panel vhost, :80/:443) ──► gisila-panel :8000 │ writes rows │ pushes jobs ▼ ▼ Postgres Redis ◄── log pub/sub │ BLPOP ▼ gisila-worker (root) │ exec ▼ gisila-agent (root) │ ┌──────────────┬──────────────┬────────────────┼──────────────┐ useradd systemd units Nginx vhosts AppArmor certbot / ufw │ ▼ app_ab12cd ─ gisila-app_ab12cd.service (one Linux user + unit per app)Requests become jobs
Section titled “Requests become jobs”The API never changes the host directly. A request such as “deploy app 42”
inserts a Deployment row with status queued, sets the app to building,
and pushes a JSON message onto a Redis list (gisila:queue:deployments). The
API answers immediately. The worker, blocked in BLPOP, picks the job up and
runs the agent’s subcommands in order. Lifecycle actions, vhost rewrites,
certificate issuance, firewall changes, database operations and service
installs all follow the same pattern, each with its own queue.
That’s why most mutating routes return {"detail": "… requested."} or a row
in a pending state. Watch the row’s status, or open the matching WebSocket,
to see the outcome.
The agent
Section titled “The agent”gisila-agent is a single Dart binary with subcommands (provision, build,
apply-unit, apply-vhost, activate-release, issue-cert, restart,
app-health, firewall, postgres, and others). Every subcommand is
idempotent, so re-running it converges on the declared state. Every argument
is validated before it reaches useradd, systemctl or certbot. See
Agent reference.
The worker runs as root so that it can call the agent directly without
sudo. Many VPS providers set the kernel’s no_new_privileges flag, which
blocks sudo from a sandboxed service.
Per-app layout
Section titled “Per-app layout”Each app gets a Linux user app_<6 chars> and a directory under /srv/apps:
/srv/apps/app_ab12cd/├── .env # app env vars, mode 0640, loaded by systemd EnvironmentFile=├── current/ # the live build (read-only to the app)├── releases/ # kept builds for instant rollback (read-only to the app)├── shared/ # persistent read-write data (uploads, media)├── tmp/ # scratch, also HOME for the process└── logs/ # optional log directoryThe app can only write to shared/, tmp/ and logs/.
The systemd unit
Section titled “The systemd unit”The agent writes gisila-app_<id>.service (part of gisila-apps.target).
Abridged:
[Service]Type=simpleUser=app_ab12cdWorkingDirectory=/srv/apps/app_ab12cd/currentExecStart=/srv/apps/app_ab12cd/current/appRestart=alwaysEnvironment=PORT=4001Environment=GISILA_APP_ID=42EnvironmentFile=/srv/apps/app_ab12cd/.envNoNewPrivileges=trueProtectSystem=strictProtectHome=truePrivateTmp=trueRestrictNamespaces=trueSystemCallArchitectures=nativeReadWritePaths=/srv/apps/app_ab12cd/shared /srv/apps/app_ab12cd/tmp /srv/apps/app_ab12cd/logs …ReadOnlyPaths=/srv/apps/app_ab12cd/current /srv/apps/app_ab12cd/releasesAppArmorProfile=gisila-app_ab12cdMemoryMax=256MCPUQuota=50%TasksMax=100If the process fails 5 times within 300 seconds, systemd parks the unit in
failed instead of restarting it forever, and the panel reports the app as
crashed.
For a web app, the agent writes a vhost that proxies the app’s domains to
127.0.0.1:<port>. Static sites are served straight from the release
directory with no process. The panel itself sits behind its own vhost
(/etc/nginx/sites-available/gisila-panel) that proxies to 127.0.0.1:8000
and passes WebSocket upgrades through.
Live logs
Section titled “Live logs”Build output from the agent is stored as BuildLog rows and published on
gisila:logs:build:<deploymentId>. The WebSocket
/ws/apps/{id}/build-logs/{deploymentId} bridges that channel to the browser.
Runtime logs come from journald through the agent’s logs subcommand. See
View logs and metrics.
Health monitoring
Section titled “Health monitoring”A health monitor in the worker probes the mail stack, each managed service and
each installed runtime toolchain on a timer. It caches the results in Redis,
and auto-repairs failures (restart, then reinstall) with a cooldown. The
…/health routes read that cache.
Example
Section titled “Example”What happens when you click Deploy on a git app:
POST /apps/42/deployments/insertsDeployment(queued)and pushes a job.- The worker runs
gisila-agent provision(user, directories,.env). gisila-agent buildclones the repo and runs the runtime’s build step as the app’s user.apply-unitwrites the AppArmor profile and systemd unit. For awebapp,apply-vhostfollows.restartstarts the new build, thenapp-healthchecks that it’s serving.- On success the deployment becomes
succeededand active, and the app becomesrunning. If the health check fails, the deployment isfailedand the appcrashed, with the journal tail in the build log.