Skip to content

Databases and services

Besides apps, a panel node can run shared infrastructure for the apps on it. None of it belongs to a team, so only superusers manage it:

Area What you get API
Applications (runtimes) Toolchains for dart, go, rust, node, bun, python, celery, zig, binary, static, several versions side by side /applications
PostgreSQL Clusters for versions 14 to 18, each on its own port. Databases with a login role, extensions, role attributes, tunable settings, backups, restores, backup schedules, optional public TLS exposure /databases
MongoDB Instances for versions 6.0, 7.0 and 8.0, databases with an authenticated user, roles, backups, schedules, public exposure /mongo
Managed services redis, memcached, smtp relay settings, mailpit, pgbouncer, pgadmin, mongo-express /services
Object storage A self-hosted Garage S3 server or an external S3 endpoint, buckets with scoped keys, buckets linked to apps /storage
Mail Postfix, Dovecot and OpenDKIM for your domains: mailboxes, DKIM keys and the DNS records to publish /mail
  • You want each app’s database on the same box, with backups, without running a separate database server.
  • An app needs Redis for caching or a gisila_queue broker.
  • An app stores uploads in S3 and you want credentials injected automatically.
  • You want to send and receive mail for your domains from the same server.
  • For production data you can’t afford to lose, a managed database with point-in-time recovery may be the better trade. Apps can connect to any external database through env vars.
  • Don’t point apps at the panel’s own Redis or Postgres. Install separate ones for apps. The panel’s Redis holds its job queues, and an app with access could inject jobs.

Every instance the panel installs is created by the agent on this host, gets its own systemd unit and data directory, and listens on 127.0.0.1 unless you expose it. Apps on the same host reach it at 127.0.0.1:<port>. The sandbox allows outbound TCP, so no extra wiring is needed.

  1. POST /databases installs a version ({"version": 16, "displayName": "Main"}). The first instance becomes the default.
  2. POST /databases/{id}/dbs creates a role and database. Leave out password and the panel generates one. The response includes a connection object with host, port, database, username, password and a ready url (postgresql://…). Copy the URL into the app’s DATABASE_URL env var.
  3. Backups (full, schema or data) are gzip’d dumps stored under /var/lib/gisila/backups, downloadable, and restorable in place. A schedule (hourly, daily or weekly, keeping the newest keepCount) runs them automatically.

The panel also lists its own cluster (the one in /etc/gisila/database.yaml) as a read-only “system” instance. You can’t stop, uninstall or expose it. If it lives on another host, the panel can only read its metrics and back it up. It can’t create databases there or restore into it.

Public exposure. POST /databases/{id}/expose with {"isPublic": true, "domain": "db.example.com"} enables TLS, adds an SSL-only hostssl rule and opens the port in ufw. The connection info then includes a publicUrl with sslmode=verify-full.

The same model, with a database plus an authenticated user and a list of built-in roles (["readWrite"]).

GET /services/catalog lists each service type with its config schema. POST /services installs one:

{
"serviceType": "redis",
"displayName": "App cache",
"config": { "port": "6380", "password": "<long-random>", "maxmemory": "256mb" }
}

The Redis service runs as its own systemd instance, separate from the panel’s Redis. It defaults to port 6380 and bind address 127.0.0.1. An empty password disables authentication. Set one, since every app on the host can reach loopback.

The worker installs the service in the background. Install output streams on the WebSocket /ws/services/{id}/logs. Health is probed periodically (GET /services/{id}/health), and an unhealthy service is restarted, then reinstalled.

Install Garage (POST /storage/providers/garage) or register an external S3 endpoint (POST /storage/providers/external). Create a bucket to get a scoped access key. Linking a bucket to an app (POST /storage/apps/{appId}/links, superuser) injects these env vars, with the prefix you choose (default S3):

S3_ENDPOINT S3_REGION S3_BUCKET S3_ACCESS_KEY S3_SECRET_KEY
S3_PUBLIC_URL S3_FORCE_PATH_STYLE

Apps can only target an installed runtime. A superuser installs a runtime with a version, can install more versions next to it, picks the default version for new apps, and can set default build and start commands. A version can’t be removed while an app pins it.

Give an app its own Postgres database:

Terminal window
H='Authorization: Bearer gsl_xxxxxxxxxxxxxxxx' # a superuser's token
J='Content-Type: application/json'
curl -sX POST https://panel.example.com/databases/1/dbs -H "$H" -H "$J" \
-d '{"dbName": "billing", "roleName": "billing"}' | jq -r .connection.url
# postgresql://billing:<generated>@127.0.0.1:5432/billing
curl -sX POST https://panel.example.com/apps/42/envs -H "$H" -H "$J" \
-d '{"name": "DATABASE_URL", "value": "postgresql://billing:<generated>@127.0.0.1:5432/billing", "isSecret": true}'