App runtime contract
This page lists what your app can rely on when the panel runs it, and what it has to do to be considered healthy. There is no build or run config file to add to your repository: everything is set on the app in the panel (or through the Apps API).
Environment
Section titled “Environment”Variables the panel sets
Section titled “Variables the panel sets”| Variable | Value | When |
|---|---|---|
PORT |
The app’s internalPort |
Apps with a port: web, tcp, internal. Not static or process. |
GISILA_APP_ID |
The app’s numeric id | Always |
HOME |
<workDir>/tmp |
Always. XDG_CONFIG_HOME, XDG_CACHE_HOME, XDG_DATA_HOME and XDG_STATE_HOME point inside it. |
PATH |
The runtime’s bin directory, then /usr/local/bin:/usr/bin:/bin |
Runtimes with a toolchain directory |
MEDIA_ROOT, MEDIA_URL |
<workDir>/shared/media, /media/ |
When mediaEnabled is on, unless you set them |
<PREFIX>_ENDPOINT, _REGION, _BUCKET, _ACCESS_KEY, _SECRET_KEY, _PUBLIC_URL, _FORCE_PATH_STYLE |
The linked bucket’s credentials | When a bucket is linked (default prefix S3) |
Your own env vars are written to <workDir>/.env (mode 0640, one
KEY="value" per line) and loaded with systemd’s EnvironmentFile=. They
override values the panel sets for the same name. They’re refreshed on each
deploy, not on restart. The same file can be loaded in a shell with
set -a; source .env; set +a.
Env vars are also present during the build.
Variables the panel reads from your app’s env
Section titled “Variables the panel reads from your app’s env”| Variable | Effect |
|---|---|
GISILA_SYSCALL_FILTER=off |
Drops the seccomp syscall filter for this app. |
FLOWER_BASIC_AUTH=user:password |
Celery apps: Flower UI credentials (generated if unset). |
Network
Section titled “Network”- Listen on
PORT. For awebapp,127.0.0.1:$PORTis enough: Nginx proxies the app’s domains to it. Binding0.0.0.0also works but exposes the port whenever the host firewall allows it. - A
tcpapp must bind0.0.0.0:$PORT. Public reachability is controlled by the panel’s ufw rule. - An
internalapp is reachable by other processes on the host at127.0.0.1:$PORT. - A
processapp gets no port and must not need one. - Behind Nginx, the client address is in
X-Real-IPandX-Forwarded-For, and the original host inHost.
Outbound connections are allowed (AF_INET, AF_INET6, AF_UNIX,
AF_NETLINK).
Filesystem
Section titled “Filesystem”| Path | Access | Use |
|---|---|---|
<workDir>/current |
read-only | The live build. The working directory for compiled runtimes. |
<workDir>/releases |
read-only | Kept builds |
<workDir>/shared |
read-write | Persistent data across deploys (uploads, SQLite files) |
<workDir>/tmp |
read-write | Scratch space and HOME |
<workDir>/logs |
read-write | Optional log files |
/tmp |
private | A per-service private /tmp |
<workDir> is /srv/apps/app_<6 chars>. Everything else on the host is
read-only or invisible. Python, Node, Bun and Rust apps run with their working
directory in the source tree, which is also writable for Node and Bun.
Start command
Section titled “Start command”With no startCommand, the runtime’s default runs:
| Runtime | Default |
|---|---|
dart, go, zig, binary |
<workDir>/current/app (the compiled or uploaded executable) |
node |
Detected from the project (for example Next, Nuxt or npm start) |
bun |
bun run start |
python |
gunicorn serving wsgiApp (pythonMode wsgi or asgi) |
celery |
Celery workers, optional beat, and Flower |
rust |
None. Set one. |
static |
No process |
A start command is typed as you would in a shell inside the project. The agent rewrites its first word to an absolute path, because systemd doesn’t search the project’s tooling:
| You type | Becomes |
|---|---|
app (compiled runtimes) |
<workDir>/current/app |
gunicorn app:application |
<venv>/bin/gunicorn app:application |
python manage.py migrate |
<venv>/bin/python manage.py migrate |
next start -p 3000 |
<node>/bin/node <src>/node_modules/.bin/next start -p 3000 |
npm start |
<node>/bin/node <node>/bin/npm start |
api (rust) |
<src>/target/release/api |
/opt/x/bin/x |
unchanged |
Rules:
- Start and build commands can’t contain
|,&,;,$,<,>, backticks or control characters. Build commands therefore can’t chain steps. Put them in a script. tcp,internalandprocessapps onpython,node,bunorrustmust have a start command.- Every rewrite is logged in the build output as
[agent] ExecStart: "…" → "…".
- Builds run as the app’s Linux user, in the source directory (or
sourceSubdir), with the app’s env vars and the pinned toolchain onPATH. - Compiled runtimes must leave the executable at
build/app, at the path given to-o/--outputin the build command, or atbin/server.exe,bin/server,build/serverorbuild/server.exe. It’s installed as<workDir>/current/app. - The Dart build sets
PUB_CACHE=<workDir>/.pub-cache. - Dependency and build caches are reused across deploys unless the deployment
sets
forceRebuild.
Health
Section titled “Health”After every deploy and rollback, the app must pass the health check within 45 seconds and stay healthy for 5 consecutive seconds:
- Its systemd unit(s) are active and their restart counter doesn’t climb.
- If it has a port,
127.0.0.1:$PORTaccepts a TCP connection. - If it’s a
webapp with ahealthCheckPath, aGETof that path on127.0.0.1:$PORTreturns a status below 500. Redirects aren’t followed.
Use a cheap, unauthenticated path such as /healthz. In a Gisila app,
AppConfig(healthCheckPath: '/healthz') serves one (see the
framework docs).
Lifecycle and shutdown
Section titled “Lifecycle and shutdown”| Setting | Value | Meaning |
|---|---|---|
Restart=always, RestartSec=5 |
A crashed process restarts after 5 seconds. | |
StartLimitBurst=5 in StartLimitIntervalSec=300 |
After 5 failures in 5 minutes the unit is parked as failed and the app shows crashed. |
|
KillMode=mixed, KillSignal=SIGTERM |
On stop or restart, only the main process gets SIGTERM, so prefork servers can drain their workers. |
|
TimeoutStopSec=30 |
Anything still running 30 seconds later is killed. |
Handle SIGTERM by finishing in-flight work in under 30 seconds.
Resource limits
Section titled “Resource limits”| Setting | From | Default |
|---|---|---|
MemoryMax |
memoryMbLimit |
256 MB |
CPUQuota |
cpuQuotaPercent (percent of one core) |
50% |
TasksMax |
tasksLimit (processes and threads) |
100 |
LimitNOFILE |
exposure mode | 4096 for web, 65536 otherwise |
Hitting MemoryMax gets the process OOM-killed inside its own cgroup. Count
threads against TasksMax: runtimes such as the Dart VM, Node and the JVM
start several threads even for one process.