Networking, domains and TLS
What it is
Section titled “What it is”Every app has an exposure mode, chosen when you create it. It can’t be changed afterwards. The mode decides how the app reaches the network:
| Mode | Port | Nginx / domains | Reachable from | Firewall |
|---|---|---|---|---|
web (default) |
yes | vhost, custom domains, TLS | the internet through Nginx on 80/443 | nothing app-specific |
tcp |
yes | none | the internet directly on the app’s port | ufw allow <port>/tcp while publiclyReachable is true |
internal |
yes | none | other processes on this host via 127.0.0.1:<port> |
never opened |
process |
none | none | nothing, it’s a long-running process | never opened |
Static sites are always web. Celery apps can’t use process (they already
run portless workers, and their port belongs to Flower).
When to use it
Section titled “When to use it”web: HTTP APIs and websites. This is the only mode that can have domains and TLS.tcp: protocols that aren’t HTTP, such as game servers, MQTT, gRPC without a proxy, or custom binary protocols.internal: a service only your other apps call, such as an internal API or sidecar.process: queue workers, bots, schedulers and scrapers that listen on nothing. See Run gisila_queue workers.
When not to use it
Section titled “When not to use it”- Don’t use
tcpfor an HTTP service just to skip Nginx. You lose TLS termination, domains and the health check path. - A
tcp,internalorprocessapp onpython,node,bunorrustmust have a start command. The default for those runtimes starts an HTTP server (gunicorn or the detected web framework), which is the wrong program. The API refuses to create such an app without one.
How it works
Section titled “How it works”Apps with a port pick it themselves (internalPort, 1024 to 65535). It must
be unique on the host. The panel passes it to the process as the PORT
environment variable. See the App runtime contract.
Web apps and domains
Section titled “Web apps and domains”For a web app, the agent writes an Nginx vhost that proxies the app’s
domains to 127.0.0.1:<port>.
To attach a domain:
- Point a DNS
A(andAAAAif you use IPv6) record for the hostname at the server.GET /system/inforeturns the address to use (publicIp, overridable withPUBLIC_IPin/etc/gisila/.env). POST /apps/{id}/domains/with{"hostname": "api.example.com"}. The worker rewrites the vhost. A hostname can belong to only one app.POST /apps/{id}/domains/{domainId}/sslqueues certificate issuance. The agent runscertbot --nginx -d <hostname>and reloads Nginx. The domain’sssl_statusmoves fromnonetopending, then toissuedorfailed.
Issuance uses the HTTP-01 challenge, so the hostname must already resolve to this server and port 80 must be reachable. Certificates are renewed by certbot’s own timer, not by the panel. The same route re-issues a certificate.
Domains are rejected for tcp, internal and process apps, since there is
no vhost to attach a hostname to.
Static sites, media and uploads
Section titled “Static sites, media and uploads”- A
staticapp is served straight from its published release.staticRootpicks the directory inside the repo (for exampledist), andstaticSpa: truefalls back toindex.htmlfor unknown paths. Changing either re-renders the vhost without a rebuild. - With
mediaEnabled: true, awebapp gets a persistentshared/mediadirectory. Nginx serves it publicly at/media/and through an internal/_protected/location the app can hand off to withX-Accel-Redirectfor downloads that need auth. The app receivesMEDIA_ROOTandMEDIA_URLenv vars.mediaMaxUploadMb(default 25) setsclient_max_body_size.
TCP apps
Section titled “TCP apps”A tcp app binds 0.0.0.0:<port> itself. After each deploy, and whenever
you call POST /apps/{id}/network with {"publiclyReachable": …}, the
worker opens or closes ufw for that port, with no rebuild or restart.
Closing a port never touches the host’s SSH ports. Non-web apps also get
LimitNOFILE=65536 instead of 4096, since every client connection is a
socket held by the app.
Restricting who can reach 80 and 443
Section titled “Restricting who can reach 80 and 443”If all your sites are proxied through Cloudflare, a superuser can limit ports
80 and 443 to Cloudflare’s ranges with POST /firewall/web-sources
({"preset": "cloudflare"}). Sites whose DNS isn’t proxied through those
sources stop loading, and their HTTP-01 renewals fail. See
Change the firewall safely.
Example
Section titled “Example”Attach a domain and issue a certificate:
H='Authorization: Bearer gsl_xxxxxxxxxxxxxxxx'J='Content-Type: application/json'
DOMAIN_ID=$(curl -sX POST https://panel.example.com/apps/42/domains/ \ -H "$H" -H "$J" -d '{"hostname": "api.example.com", "isPrimary": true}' | jq .id)
curl -sX POST "https://panel.example.com/apps/42/domains/$DOMAIN_ID/ssl" -H "$H"
curl -s https://panel.example.com/apps/42/domains/ -H "$H" | jq '.results[] | {hostname, ssl_status}'