Skip to content

Services

A service is a long-running process — a web server, notebook, API, or viewer — that runs until the user explicitly stops it. Services are declared with type: service on the runnable:

runnables.yaml
runnables:
- id: notebook
name: JupyterLab
type: service
command: jupyter lab --no-browser --ip=0.0.0.0 --port=0
resources:
cpus: 4
memory: "32 GB"
walltime: "08:00"

From the cluster’s perspective, a service is just a long-running batch job. The difference is in how Fileglancer communicates the service URL to the user:

  1. The user launches a service-type runnable → the job enters PENDING.
  2. The cluster picks it up → RUNNING.
  3. The service starts and publishes its URL: either Fileglancer writes SERVICE_URL_PATH for auto_url services, or the service writes that file itself.
  4. On the next poll (every few seconds), Fileglancer reads the file and displays the URL in the UI.
  5. The user clicks Open Service → the service opens in a new browser tab.
  6. When done, the user clicks Stop Service → the job is killed and the URL disappears.

The Easy Path: Let Fileglancer Manage the Port and URL

Section titled “The Easy Path: Let Fileglancer Manage the Port and URL”

For most services you don’t need to write any port-discovery or URL-writing code. For every service-type job Fileglancer exports these helper variables into the job environment:

  • FG_SERVICE_PORT — a free TCP port picked on the compute node at job start.
  • FG_HOSTNAME — the compute node’s hostname.
  • FG_SERVICE_TOKEN — a URL-safe random secret you can use for auth.

Bind your service to $FG_SERVICE_PORT and set auto_url: true. Fileglancer then waits until that port is accepting connections and only then writes http://$FG_HOSTNAME:$FG_SERVICE_PORT to SERVICE_URL_PATH — so the Open Service link never appears before your service (or a still-pulling container image) is ready. While a first-launch container image is still downloading, the job page shows a “Downloading container image…” message so the wait is explained rather than mysterious.

Because the port is chosen before your command runs and substituted straight into the command line, this works even for container runnables — no wrapper script inside the image is needed:

runnables.yaml
runnables:
- id: serve
name: Server
type: service
auto_url: true
container: ghcr.io/coder/code-server:latest
command: code-server --bind-addr 0.0.0.0:$FG_SERVICE_PORT --auth none
requirements:
- apptainer
resources:
cpus: 2
memory: "8 GB"
walltime: "08:00"

Most servers can take an auth token in the URL. Pass $FG_SERVICE_TOKEN to your server, and add a service_url_suffix that splices the token into the published URL — clicking Open Service then logs straight in, no password prompt, while the session stays protected by the secret:

runnables.yaml
runnables:
- id: serve
name: Server
type: service
auto_url: true
service_url_suffix: "/?access_token=${FG_SERVICE_TOKEN}"
command: marimo edit --headless --host 0.0.0.0 --port $FG_SERVICE_PORT --token-password="$FG_SERVICE_TOKEN"
requirements:
- pixi

service_url_suffix is a small template: literal URL text plus the placeholders ${FG_SERVICE_TOKEN}, ${FG_SERVICE_PORT}, and ${FG_HOSTNAME} (braces required). It’s appended to http://$FG_HOSTNAME:$FG_SERVICE_PORT, so it can carry a path as well as a query — e.g. JupyterLab uses "/lab?token=${FG_SERVICE_TOKEN}".

If your service picks its own port, needs a readiness check more specific than “port is open” (e.g. an HTTP health path), or serves an https/otherwise non-standard URL, write SERVICE_URL_PATH yourself instead of using auto_url (the two are mutually exclusive). Fileglancer exports SERVICE_URL_PATH — the absolute path to a file where your service should write its URL once it is ready to accept connections.

In Python:

import os, socket
url = f"http://{socket.gethostname()}:{port}"
service_url_path = os.environ.get("SERVICE_URL_PATH")
if service_url_path:
with open(service_url_path, "w") as f:
f.write(url)

In Bash:

Terminal window
echo "http://$(hostname):${PORT}" > "$SERVICE_URL_PATH"
  • Startup: with auto_url, Fileglancer writes SERVICE_URL_PATH once $FG_SERVICE_PORT accepts connections. Without auto_url, write the URL to SERVICE_URL_PATH yourself as soon as the service is ready. Until the file exists, the UI shows “Service is starting up…”.
  • Running: Fileglancer reads the URL file on each poll. If the URL changes (e.g. a port rebind), the UI updates automatically.
  • Shutdown: when the user clicks Stop Service, Fileglancer sends a SIGTERM to the job. Handle this signal for graceful shutdown. Cleaning up the URL file on exit is good practice but not required — Fileglancer only reads it while the job status is RUNNING.
  • Port selection: use $FG_SERVICE_PORT with auto_url, or port 0 if your service writes the actual chosen URL to SERVICE_URL_PATH itself.
  • Walltime: set a generous walltime — services run until stopped, but the cluster will kill them when walltime expires. Consider "08:00" or longer for interactive sessions.
  • Flush output: under a batch scheduler like LSF, Python’s stdout may be buffered. Use flush=True on print statements or set PYTHONUNBUFFERED=1 so logs appear in real time.
runnables.yaml
name: My Viewer
description: Interactive data viewer
runnables:
- id: view
name: Start Viewer
type: service
auto_url: true
description: Launch an interactive viewer for browsing datasets
command: pixi run python start_viewer.py --host 0.0.0.0 --port $FG_SERVICE_PORT
parameters:
- flag: --data-dir
name: Data Directory
type: directory
description: Directory containing datasets to view
required: true
resources:
cpus: 2
memory: "8 GB"
walltime: "08:00"