There are two ways to run Colloq at a university; pick the one that fits your infrastructure.
In your Kubernetes
A Helm chart in one namespace: no cluster-admin, restricted Pod Security, your own registry, storage, ingress and secrets from Vault. For organisations whose platform team runs the cluster.
B / ONE MACHINEOn one VM
The ready-made image and colloq-host on a dedicated Linux machine behind the university's reverse proxy. The rest of this page is about that.
In short for IT
| What it is | Colloq is a collaborative Jupyter classroom: a web app plus a separate container with a Python kernel for each room. Open source, MIT license. |
|---|---|
| Machine | One dedicated x86-64 VM with Ubuntu 22.04 or 24.04 and Docker Engine 28 or newer. The app controls Docker, which is root on the VM, so run nothing else on it. |
| Resources | Start with 16 vCPU, 64 GB RAM, 250 GB NVMe, no GPU. That covers a group of 30 with personal notebooks or with a competition, and a lecture for 100; the reasoning is under Machine and sizing. |
| Inbound | Only HTTPS from users' browsers to your reverse proxy; the proxy reaches the app at 127.0.0.1:3000. If users are on the campus network or VPN, nothing is needed from the internet. |
| Outbound | Nothing at run time for the core features: no telemetry, no update checks, no CDN. Installing and updating need ghcr.io — or your registry mirror, or carrying the images over as a file. Optional: an AI model (OpenAI or your own on the internal network), notebook import from GitHub, PyPI or an internal mirror for competition packages, internet access for students' code. |
| Data | All on this VM under /workspace/colloq: the SQLite database, room files, competition data, backups. Settings are in /etc/colloq/colloq.env. |
| Backups | colloq-host backup by hand, or colloq-host backup-timer on for one every night. Copying the backups off the machine is up to your own tools. |
How it fits together
The university's reverse proxy terminates HTTPS and forwards requests to the app. The app is one container, colloq, from the image ghcr.io/colloq-edu/colloq-server: a Node.js server, the built web interface and an SQLite database. Its port 3000 is published on 127.0.0.1 of this VM only. The container is started and updated by colloq-host, a small script on the host that is installed from the same image.
For each room the app starts a kernel container of its own on the same Docker: its own memory, CPU and process limits, only that room's folder mounted, uid 1000, no Linux capabilities. Students' personal notebooks run in a second container per class; a competition submission runs in two throwaway containers with no network. The app and the room kernels share the Docker network colloq.
The state directory is /workspace/colloq: data/ (the database and keys), workspace/<room>/ (room files), environments/ (environments created in the panel) and backups/. It is mounted into the container at the same path. Settings live in /etc/colloq/colloq.env (mode 0600) and the chosen image in /etc/colloq/image. Mount a separate disk or partition at /workspace/colloq, so that filling it up doesn't take the system down with it.
Machine and sizing
Start here: 16 vCPU, 64 GB RAM, 250 GB NVMe, x86-64 (the image is built for amd64 only), Ubuntu 22.04 or 24.04, Docker Engine 28 or newer, no GPU. The clock must be synchronised over NTP (from an internal server if there is no internet): sign-in links, cookies and certificates depend on the time. Docker must manage iptables itself: the room network block goes into its DOCKER-USER chain, so "iptables": false in daemon.json and Docker 29's nftables backend won't do. The machine's memory and CPU go to room kernels and submissions; the numbers for those below are derived from the limits in the code. The app itself is light, and that was measured.
| Scenario | vCPU | RAM | Disk |
|---|---|---|---|
| (a) 30 students, one shared notebook | 4 | 8 GB | 60 GB |
| (b) 30 students, each with a personal notebook | 16 | 64 GB (48 GB at least) | 100 GB |
| (c) 30 students and a competition, 2-core / 2 GB submissions | 16 (auto gives 7 slots) | 32 GB | 100 GB |
| (d) three (c) classes in the same hour | 32 (about 15 slots) | 64 GB; 160–192 GB if all three use personal notebooks | 200 GB |
| (e) 100 people in a lecture room | 4 (8 if students run cells) | 8 GB | 60 GB |
The starting point covers (a), (b), (c) and (e), and (d) with a slower submission queue. Assumptions: pandas and scikit-learn at the default settings, and a submission that runs for about two minutes.
The app, measured (30 Sep 2026, a 21 vCPU / 49 GB VM, nginx in front, make load): 500 people joined one room within a minute with no refusals, a median join taking 7 ms. The app process held 243 MB of memory and 20 % of one core at rest, and half a core with twenty people typing at once; an edit reached everyone else within 0.17 s (p95). At 300 people: 198 MB and the same shares of a core.
Work out RAM like this: about 3 GB for the system and the app + room limits (4 GB per room by default) + personal-notebook container limits + slots × (submission memory + 0.25 GB) + 1.6 GB while packages are being prepared. Docker reserves nothing, so Colloq itself refuses to start a kernel whose limit, together with the limits of the containers already running, doesn't fit in the machine's memory minus 1 GB. CPU is shared, not reserved: a room is capped at 2 cores.
- Personal notebooks: about 1–1.5 GB and 0.25 core per student. All personal notebooks of a class live in one container, which by default gets as much as the room — 4 GB for everyone together. For a group, raise Memory per class in the panel: Resources → Students' personal notebooks (or in a particular class's rules).
- Competitions: how many submissions run at once (Executors at once in the Resources tab) is worked out by default: the smaller of (cores − 2) / submission cores and (memory − 2 GB) / (submission memory + 0.25 GB), and never more than 32. Thirty two-minute submissions take about 9 minutes on 7 slots and about 20 minutes on 3 slots (an 8 vCPU / 16 GB machine).
- A room keeps its memory while anyone is in it and for 2 hours after everyone has left: the end of a class doesn't stop its kernel. Back-to-back classes therefore overlap.
- Disk: the kaggle-base image takes about 1 GB and base-gpu about 9 GB; after that, room files, competition submissions and backups grow. Class pages keep their own copies of the published notebooks and files in
data/page-files/: about 0.3–0.8 GB per 33-class course, with a file used on several pages stored once. There is no per-room quota; see monitoring. - A GPU is optional. Only GPU environments (base-gpu) need one: a card, 16 GB RAM and about 10 GB of disk for each GPU room running at the same time, plus the NVIDIA driver and the NVIDIA Container Toolkit (
colloq-hostinstalls the toolkit itself through apt). Personal notebooks and competition submissions never get a GPU.
The Resources tab has a memory bar: running rooms, personal notebooks and competition slots against the machine's memory minus 1 GB; when memory runs short, the bar says so. It also shows free disk on the data partition, with a warning under 15 % or 10 GB free; it does not show GPUs.
Inbound and outbound connections
Inbound connections:
| From and to | Port | Purpose |
|---|---|---|
| Users' browsers → reverse proxy | 443/TCP; 80 only to redirect to HTTPS | The whole app: pages, API, WebSocket, server-sent events. |
| Reverse proxy → app | 127.0.0.1:3000 if the proxy is on this VM; otherwise the VM's internal address, port 3000 (COLLOQ_BIND) | Plain HTTP inside the machine or a trusted segment. |
| Administrators → VM | 22/TCP | SSH and colloq-host; from the IT network only. |
| Internet → VM | — | Not needed if users are on the campus network or VPN. Ports 80/443 from the internet are needed only if the proxy gets its own Let's Encrypt certificate. |
The kernels' Jupyter and the other internal ports are not published: they live inside the Docker network. Nothing calls out: the app serves its fonts, PDF viewer and plotly itself, and browsers only ever reach the Colloq address.
Outbound connections:
| Destination | Purpose and when | Required | Can it point elsewhere |
|---|---|---|---|
ghcr.io, pkg-containers.githubusercontent.com | The app image colloq-server and the kernel images colloq-kernel: installing and updating | Yes, unless you carry the images over as a file | Yes: a registry mirror in COLLOQ_IMAGE and KERNEL_IMAGE_REPO; without a network, a file |
get.docker.com, download.docker.com | Installing Docker if the machine has none | No, if Docker is already installed | Install Docker from your own repository |
Docker Hub (registry-1.docker.io, auth.docker.io, production.cloudflare.docker.com), deb.debian.org, pypi.org, files.pythonhosted.org | Building a kernel image on the machine: an environment from the panel, or a published image that couldn't be pulled | No | Partly: --index-url lines in the environment's package list; ready kernel images from a registry |
nvidia.github.io | The NVIDIA Container Toolkit, on the first install on a GPU machine | GPU only | Install the package in advance |
The AI model, api.openai.com by default | The Oracle, when someone asks it | No | Yes: OPENAI_BASE_URL or the panel — Ollama, vLLM or another OpenAI-compatible address on the internal network |
api.github.com, raw.githubusercontent.com | Importing a notebook from GitHub in the panel, on request | No | No, github.com only |
pypi.org, files.pythonhosted.org | Competition entrants' own packages, when a set is prepared | No | Yes: DEPENDENCY_INDEX_URL, DEPENDENCY_FILES_HOSTS |
| The internet from room kernels | pip install, datasets and APIs in students' code, during class | No | Can be cut off: COLLOQ_ROOM_NETWORK=none, see below |
Network for students' code
- Competition submissions run with no network at all.
- Rooms and personal notebooks reach the internet by default (
pip install, datasets, APIs), but private addresses are closed to them: 10/8, 172.16/12, 192.168/16, 100.64/10, 169.254/16 with the cloud metadata, 127/8, multicast, the VM itself and neighbouring rooms. A connection there is refused at once; only DNS on port 53 stays open to any address. The app installs the rules itself, in iptables chains of its own,COLLOQ-ROOMS-*, reached fromDOCKER-USERandINPUT. If they can't be installed, rooms don't start. - The university's public ranges. Students' code goes out from the VM's address, so services that trust university addresses (library subscriptions, internal sites on public addresses) will take it for one of their own. List those ranges:
KERNEL_BLOCKED_CIDRS=192.0.2.0/24,198.51.100.0/24. COLLOQ_ROOM_NETWORK=nonegives room and personal-notebook kernels no outbound network at all; only the Colloq server reaches them.pip installin a cell then doesn't work: put the libraries you need into an environment in advance (panel → Environments).COLLOQ_ROOM_NETWORK=openlifts the private-address block; use it only for trusted groups that need the local network.
Outbound proxy, internal CA and PyPI mirror
If the way out is only through a proxy, set HTTPS_PROXY, HTTP_PROXY and NO_PROXY (lowercase works too). All of the server's outbound HTTP — to the AI model and to GitHub — goes through the proxy, and pip gets the same values when it prepares packages. Private addresses and Colloq's own internal names (kernels, containers) bypass the proxy by themselves; list internal domains in NO_PROXY, such as your model's and your PyPI mirror's. NODE_EXTRA_CA_CERTS is the path to a PEM file with your internal CA's certificates on the host: colloq-host mounts it into the container at the same path, and pip gets it too. Put the same file into the system store as well: Docker pulls the images itself and needs both this CA and a proxy of its own.
cp campus-ca.pem /usr/local/share/ca-certificates/campus-ca.crt
update-ca-certificates # for Docker's own image pulls
# together with the other settings of colloq-host up, see Installation
HTTPS_PROXY=http://proxy.example.edu:3128
HTTP_PROXY=http://proxy.example.edu:3128
NO_PROXY=localhost,127.0.0.1,.example.edu
NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/campus-ca.crtDocker's proxy is the proxies key in /etc/docker/daemon.json (or a systemd drop-in for docker.service); after that change and after update-ca-certificates, restart Docker.
{
"proxies": {
"http-proxy": "http://proxy.example.edu:3128",
"https-proxy": "http://proxy.example.edu:3128",
"no-proxy": "localhost,127.0.0.1,.example.edu"
}
}A PyPI mirror for competition entrants' own packages is set with DEPENDENCY_INDEX_URL=https://pypi.example.edu/simple; any index compatible with PyPI's Simple API will do (the default is https://pypi.org/simple). If the package files come from another host, list it in DEPENDENCY_FILES_HOSTS. The mirror's addresses may be private; the index address must be https and carry no credentials, so the mirror has to serve packages anonymously.
Installing without internet access
- On a machine with internet access and Docker, get
colloq-hostof the version you want and build a bundle: the app image, the kernel images andcolloq-hostitself in one file. Its kernels are for the default environment (base); list others inKERNEL_PRELOAD. While the file is written, it needs about twice its size in free disk. - Carry the file over to the university VM and install Docker Engine 28 or newer there from your distribution's packages or an internal mirror: without a network,
colloq-hostcan't install it. - Take
colloq-hostout of the file, load the images and start Colloq.loadputscolloq-hostinto/usr/local/sbin, remembers the image and writesCOLLOQ_OFFLINE=1: from then on nothing is pulled, installed or built from the network.
# on a machine with internet access
docker run --rm -v "$PWD:/host" ghcr.io/colloq-edu/colloq-server:X.Y.Z install-host /host
KERNEL_PRELOAD=base,kaggle-base ./colloq-host bundle colloq-X.Y.Z.tar ghcr.io/colloq-edu/colloq-server:X.Y.Z
# on the university server, as root
tar -xf colloq-X.Y.Z.tar colloq-host && ./colloq-host load colloq-X.Y.Z.tar
colloq-host doctor
COLLOQ_TUNNEL=none PUBLIC_URL=https://colloq.example.edu colloq-host upUpdates go the same way: a new bundle, colloq-host load, then colloq-host update. Without internet access you lose GitHub import, the Oracle (unless your own model runs on the internal network), competition entrants' own packages (unless you have a PyPI mirror) and building new environments from the panel, which needs Docker Hub, Debian and PyPI.
Reverse proxy
What the proxy must do:
- Serve the root of a hostname of its own, such as
https://colloq.example.edu/. The app has no base-path setting and won't work from a sub-path. - Pass WebSocket upgrades on
/collab/,/control/and/file/. The server pings every 25 s. - Not buffer server-sent events:
/api/k/competitions/<slug>/stream,/api/k/competitions/<slug>/dependencies/<set>/stream,/api/admin/competitions/<id>/stream,/api/admin/environments/<name>/log. The app marks these responses withX-Accel-Buffering: no(nginx honours it on its own) and sends a heartbeat every 20 s. - Keep WebSocket connections and streams open for hours: a read timeout of at least an hour.
- Accept request bodies of at least 210 MB: competition data is uploaded in one request of up to 200 MB. A file uploaded to a room is up to 50 MB by default (
MAX_UPLOAD_MB); if you raise that, raise the proxy's limit too. - Get an upload through to the app within 5 minutes: that is Node.js's request timeout. By default nginx first receives the whole body and only then passes it on quickly, so a slow client doesn't run into this limit; a proxy that streams the body through (Caddy) does.
- Keep idle connections to the app for less than 130 s: that is how long the app itself keeps them, and the proxy must not send a request down a connection the app is just closing.
- Pass
Hostas the client sent it: the app checksOriginagainst it, and otherwise every write under/apigets a 403. - Set
X-Forwarded-Forto the client's address (replace it, don't append) andX-Forwarded-Prototohttps; remove theCF-Connecting-IPheader.
nginx (1.18 from Ubuntu 22.04 and 1.24 from 24.04), file /etc/nginx/conf.d/colloq.conf. If the proxy is on another machine, put the VM's internal address into upstream.
map $http_upgrade $connection_upgrade {
default upgrade;
'' '';
}
upstream colloq {
server 127.0.0.1:3000;
keepalive 32;
keepalive_timeout 60s; # less than the 130 s the app keeps
}
server {
listen 80;
server_name colloq.example.edu;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2; # from nginx 1.25.1: listen 443 ssl; and http2 on;
server_name colloq.example.edu;
ssl_certificate /etc/ssl/colloq/fullchain.pem;
ssl_certificate_key /etc/ssl/colloq/privkey.pem;
client_max_body_size 256m; # competition data: up to 200 MB in one request
location / {
proxy_pass http://colloq;
proxy_http_version 1.1;
proxy_set_header Host $http_host; # with the port, if any: Origin is checked against it
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header CF-Connecting-IP "";
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 1h; # WebSocket and server-sent events
proxy_send_timeout 1h;
}
}Caddy, file /etc/caddy/Caddyfile:
colloq.example.edu {
# The university's certificate. Without this line Caddy gets a Let's Encrypt
# certificate, and then the name must be reachable from the internet on 80 and 443.
tls /etc/caddy/colloq.crt /etc/caddy/colloq.key
request_body {
max_size 256MB
}
# Caddy sets Host, X-Forwarded-For and X-Forwarded-Proto itself,
# passes WebSocket and server-sent events through with no settings,
# and keeps idle connections to the app for 2 minutes, less than 130 s.
reverse_proxy 127.0.0.1:3000 {
header_up -CF-Connecting-IP
}
}The proxy's access logs contain room addresses, staff sign-in keys (/admin/k/…, /admin/t/…) and room tokens in WebSocket addresses (?token=…). Keep them as private as the database, or log without the request address.
To check: curl -sS https://colloq.example.edu/api/readyz, then a room from two devices — shared editing, running a cell, uploading a file. Streams show in the environment build log in the panel: lines must arrive one by one, not in a single lump at the end.
Client addresses and HTTPS
The app trusts X-Forwarded-For only when the connection comes from an address in TRUSTED_PROXIES. A proxy on this same VM reaches the app through Docker's port forwarding, so the app sees the gateway of the Docker network colloq, not 127.0.0.1. While the port is published on loopback and TRUSTED_PROXIES is unset, colloq-host trusts loopback and that gateway by itself, so a proxy on the same machine needs no settings. Set TRUSTED_PROXIES yourself and this default is gone: then name the gateway too. Without a trusted proxy the whole group looks like one address: the 61st newcomer to a room within 10 minutes gets a 429, and the Oracle takes 20 questions a minute for everyone together.
# the gateway of the colloq network
docker network inspect colloq -f '{{range .IPAM.Config}}{{.Gateway}}{{end}}'By default Docker takes its own networks from 172.17.0.0–172.31.255.255 and 192.168.0.0/16. If that overlaps campus networks, set other ranges in default-address-pools in /etc/docker/daemon.json before installing.
TRUSTED_PROXIES takes IPv4 and IPv6 addresses and subnets, separated by commas or spaces, and the keywords loopback, private (all private ranges) and none. The client is the rightmost address in X-Forwarded-For that is not itself trusted.
A proxy on another machine. Publish the port on the VM's internal address, COLLOQ_BIND=10.0.0.5, and put the proxy's address into TRUSTED_PROXIES (for example TRUSTED_PROXIES=10.0.0.2); colloq-host remembers both. Close port 3000 to everyone but the proxy. ufw and firewalld rules don't see ports Docker publishes: filter in the DOCKER-USER chain or on a firewall in front of the VM.
Shared addresses. If many people come from one address — through the campus Wi-Fi NAT or a VPN concentrator — list those addresses: SHARED_ADDRESSES=198.51.100.7/32,10.20.0.0/16. Per-address caps don't apply to them: 60 newcomers to a room per 10 minutes, 20 Oracle questions a minute, competition sign-ins and joins. Per-room and per-person caps still do.
The address for links. PUBLIC_URL=https://colloq.example.edu is the external address, with no path and no trailing slash. Every link Colloq hands out is built from it, the owner link included.
HTTPS. When the proxy says X-Forwarded-Proto: https, cookies get the Secure flag and the app itself sends Strict-Transport-Security (180 days, without includeSubDomains); over plain http the header is never sent. If your proxy already adds its own, turn the app's off with HSTS=0.
Installation
Everything below runs as root on a prepared VM: Ubuntu 22.04 or 24.04, Docker Engine 28 or newer from Docker's repository or your mirror, a disk for /workspace/colloq. Take the version number from the Releases page.
# 1. colloq-host from the image of the chosen version, and a check of the machine
IMAGE=ghcr.io/colloq-edu/colloq-server:X.Y.Z
docker run --rm -v /usr/local/sbin:/host "$IMAGE" install-host /host
colloq-host doctor
# 2. Start, behind a proxy on this same VM
COLLOQ_TUNNEL=none \
PUBLIC_URL=https://colloq.example.edu \
COLLOQ_IMAGE="$IMAGE" \
colloq-host up
# 3. The owner link, readiness and nightly backups
colloq-host link
colloq-host status
colloq-host backup-timer ondoctor changes nothing: one PASS, WARN or FAIL line per check — architecture, the Docker version and its firewall, memory and disk, port 3000, DNS, clock synchronisation, access to ghcr.io, PUBLIC_URL and trusted proxies. Fix every FAIL line before the first class. up installs what is missing, pulls the image, writes the settings into /etc/colloq/colloq.env and starts the colloq container. The default kernel image (base) is pulled from ghcr.io/colloq-edu/colloq-kernel, or built on the machine if that fails; this takes a few minutes in the background, and the panel works in the meantime. If the proxy is on another machine, add COLLOQ_BIND and TRUSTED_PROXIES to up; see Client addresses.
colloq-host link prints the address and, while the server has no owner, the setup link …/admin/t/…. It is the key to the server: don't paste it into a group chat. Open it and enter your name and email, and you are the owner. Add teachers in the panel, under Who can teach: each gets a personal sign-in link.
colloq-host status shows /api/health, the kernel images and the room containers; the server is ready for a class when the answer has "ok":true. Before the first group, create two rooms, open them from two devices on the students' network, run a cell, upload a file, then test a backup and a restore on a spare VM.
Settings
colloq-host up writes the variables from its environment into /etc/colloq/colloq.env (mode 0600): a non-empty value replaces the previous one, and a missing one erases nothing. It also remembers its own settings there — COLLOQ_HOME, COLLOQ_BIND — so that a later update acts the same way. To remove a line, edit the file. Running colloq-host up again applies the changes: the app container is recreated only if the settings or the image changed, and room kernels keep running. The owner can also change the Oracle, room resources and competition slots in the panel, without a restart; what the panel saved wins over the file.
| Variable | What it sets | Default |
|---|---|---|
| Address and proxy | ||
COLLOQ_TUNNEL | none: your proxy provides the address. The other modes (relay, cloudflare, direct) are for rentals and for publishing without a proxy of your own. | auto; off Vast without PUBLIC_URL, none with a warning |
PUBLIC_URL | The external address links are built from. | — |
TRUSTED_PROXIES | Proxy addresses whose X-Forwarded-For is accepted: IPs, subnets, loopback, private, none. | loopback; with the port on loopback, colloq-host adds the gateway of the colloq network |
SHARED_ADDRESSES | NAT subnets that per-address caps don't apply to. | empty |
HSTS | 0: don't send Strict-Transport-Security. | on |
TRUST_CF_CONNECTING_IP | 1: accept CF-Connecting-IP; only for Colloq's own Cloudflare tunnels, never behind your proxy. | off |
COLLOQ_BIND | Where the host publishes port 3000; needed when the proxy is on another machine. Read by colloq-host only. | 127.0.0.1 |
| Outbound connections | ||
HTTPS_PROXY, HTTP_PROXY, NO_PROXY | The outbound proxy for the AI model, GitHub import and pip. | — |
NODE_EXTRA_CA_CERTS | The internal CA's PEM file (a path inside the container). | — |
DEPENDENCY_INDEX_URL | The index for competition entrants' own packages. | https://pypi.org/simple |
DEPENDENCY_FILES_HOSTS | Hosts that serve the package files, comma-separated. | — |
COLLOQ_ROOM_NETWORK | none: rooms get no network; open: no private-address block. | empty: internet yes, private addresses closed |
KERNEL_BLOCKED_CIDRS | Extra subnets closed to rooms. | — |
COLLOQ_HELPER_IMAGE | The image of the helper container that installs the iptables rules. | the server's own image |
COLLOQ_OFFLINE | 1: download nothing; images come through colloq-host load, which sets it. | off |
| Images and backups | ||
COLLOQ_HOME | The state directory; set it before the first up. Read by colloq-host only. | /workspace/colloq |
COLLOQ_IMAGE | The image of the first up; after that colloq-host update chooses the image (/etc/colloq/image). | — |
KERNEL_IMAGE_REPO | Where ready kernel images come from: <repo>:v<version>-<environment>; none builds every environment on the machine. | ghcr.io/colloq-edu/colloq-kernel |
COLLOQ_REGISTRY_USER, COLLOQ_REGISTRY_TOKEN | Sign-in to a private registry (a read-only token); read by colloq-host only. | — |
BACKUP_KEEP | How many recent backups to keep; 0 keeps them all. | 14 |
BACKUP_AGE_RECIPIENT | An age public key: backups are encrypted for it. Restore with BACKUP_AGE_IDENTITY, the path to the private key. | — |
COLLOQ_ALLOW_SCHEMA_DOWNGRADE | 1 opens a database written by a newer version. Without it an older image refuses such a database; the ordinary rollback is restore --image with the backup taken before the update. | — |
COLLOQ_BACKUP_AT | The time of the daily backup; read by backup-timer on. | 03:30 |
COLLOQ_BACKUP_BEFORE_UPDATE | 0: no backup before update; read by update. | 1 |
COLLOQ_FREEZE_UPDATES | 1: turn off automatic system updates and hold the NVIDIA packages. Only the Vast on-start sets it; don't set it on a university server. | off |
| Classes | ||
INSTITUTION | The name shown next to the logo. | — |
UI_LANGUAGE | The interface language (ru, en) until the owner chooses one in the panel. | ru |
TZ | The time zone: day boundaries for daily quotas, dates. | Europe/Moscow |
ADMIN_EMAIL | The email the owner form suggests in advance. | — |
MAX_UPLOAD_MB | The limit for one file uploaded to a room. | 50 |
MAX_SESSION_MB | The limit for all uploads to one room; not a disk quota. | 1024 |
KERNEL_MEM | A room kernel's memory; KERNEL_MEM_<ENVIRONMENT> for one environment. | 4g, 16g for GPU environments |
KERNEL_CPUS | CPU cores per room. | 2 |
KERNEL_OWN_MAX, KERNEL_OWN_PIDS, KERNEL_OWN_IDLE_MIN | The personal-notebook container: how many kernels it holds, its process ceiling, idle minutes before a kernel is stopped. | 60, 2048, 30 |
KERNEL_ENV, KERNEL_PRELOAD | The default environment; which environments to prepare at start. | base; same as KERNEL_ENV |
KERNEL_GPUS | Cards for GPU rooms. | all detected |
OPEN_SEMINAR_CREATION | Allow creating rooms through the API without a teacher's sign-in. | false |
SESSION_SECRET | The link-signing key; a new value invalidates every link handed out. | generated, data/session-secret |
| Oracle | ||
AI_PROVIDER | openai, ollama, vllm, openrouter or custom. | openai |
OPENAI_API_KEY | The model key; without it the Oracle is off (Ollama and vLLM work without a key). | — |
OPENAI_BASE_URL | The model's address. | https://api.openai.com/v1 |
OPENAI_MODEL | The model. | gpt-4o-mini |
| Sign-in through a proxy (SSO) | ||
AUTH_JWT_JWKS_URL, AUTH_JWT_AUDIENCE, AUTH_JWT_ISSUER and the other AUTH_JWT_* | Sign-in through Teleport or another proxy that signs a JWT: teachers are recognised by email, students join under their own name. How it works and which token fields are read is in the Kubernetes guide; on a machine the same variables go to colloq-host up. When Teleport publishes the app from this same machine: COLLOQ_TUNNEL=none, and PUBLIC_URL is the app's address in Teleport. | off |
Backups
colloq-host backup # a backup now
colloq-host backup-timer on # every day at 03:30; another time: COLLOQ_BACKUP_AT=02:00
colloq-host backup-timer statusThe timer is a systemd unit: the backup runs in the machine's time zone, and one missed while the machine was off runs as soon as it is back. A backup is a pair of files in /workspace/colloq/backups/: a consistent snapshot of the database, colloq-<time>.db, and an archive, colloq-<time>-files.tar.gz, with room files, output images, class page files, competition data and submissions, package sets, the link-signing key, the setup token and the package lists of your own environments. The files are copied live, and during a class they can change while being copied, so schedule the timer for the night. The 14 most recent backups are kept (BACKUP_KEEP); older ones are deleted after a successful backup. Kernel images are not in the backup: they are pulled or built again.
A backup is the key to the server: it holds the signing key, the setup token and the database with the teachers' sign-in keys and the AI key, if it was entered in the panel. The files are created with mode 0600. Copy them off the machine with your own tools (rsync, restic or borg into university storage) and keep them encrypted: the VM's disk is not a backup. Colloq can encrypt them itself: set BACKUP_AGE_RECIPIENT to an age public key, and both parts of a backup are written as .age. Keep the private key off the machine; to restore, name it: BACKUP_AGE_IDENTITY=/root/colloq-backup.key REPLACE=1 colloq-host restore ….
To restore, put the pair into /workspace/colloq/backups/ and run the command below. colloq-host stops Colloq, restores the database and files and starts it again; REPLACE=1 is needed when the machine already has a database. Test a restore on a spare VM at least once a term.
REPLACE=1 colloq-host restore backups/colloq-20260930-033000.db
Updates and rollback
colloq-host update ghcr.io/colloq-edu/colloq-server:X.Y.Z
colloq-host statusupdate pulls the image, takes a backup and prints the rollback command, and only then recreates the app container, so the new image meets the database after the backup. The data stays on disk, room kernels keep running and the new server reattaches to them; the break lasts seconds, and open rooms reconnect by themselves. Still, update between classes. The chosen image is written to /etc/colloq/image and survives a reboot. update installs the new colloq-host from the new image by itself. Only when coming from 0.8.x, whose colloq-host could not do that yet, install the new one by hand first: docker run --rm -v /usr/local/sbin:/host ghcr.io/colloq-edu/colloq-server:X.Y.Z install-host /host.
An older image is not tested against a database the newer version may have changed. So a rollback is the older image together with the backup taken before the update; anything done after the update is lost. This is the command update prints:
REPLACE=1 colloq-host restore --image ghcr.io/colloq-edu/colloq-server:<previous version> \
backups/colloq-<time>.dbRestarting Docker, for example when it is upgraded, stops the room kernels: notebooks and files stay, Python variables are lost. Upgrade Docker in a maintenance window. colloq-host doesn't turn off Ubuntu's automatic updates.
Monitoring, logs and reboots
| Address | What it checks |
|---|---|
/api/livez | The process is alive. |
/api/readyz | The database and the working files are fine; doesn't depend on Docker or the kernels. Good for a load balancer and external checks. |
/api/health | The full check: database, Docker and the kernel image, working files, version; 503 on failure. Error texts are shown only to staff and to colloq-host status. Right after installation it answers 503 until the kernel image is ready. |
Staff can use GET /api/instance/operations: queue age, retries, notebook save failures, memory reservations and free disk.
Disk. There is no per-room quota: the database, room files, submissions and backups share one partition, and a student who fills the disk stops notebook saving for everyone. Watch free space and inodes on the /workspace/colloq partition, with an alert at, say, 80 %; a separate partition or XFS project quotas give a hard limit.
Logs. colloq-host logs is docker logs of the colloq container; Docker keeps 5 files of 50 MB. The log contains room addresses, and a room address is a sign-in link, so access to the log equals access to the rooms.
Reboots. Docker brings the app container back by itself (--restart unless-stopped). Kernel containers don't come back: a room starts its kernel again when someone joins it or runs a cell. Notebooks and files are there; Python variables are not.
What to watch regularly: free disk; the memory bar in the Resources tab; that the nightly backup was made and copied off the machine; colloq-host status before a class.
Security and personal data
What is stored. Everything is on this VM, under /workspace/colloq:
data/colloq.db: rooms and notebooks with their edit history; the names participants typed when joining; attendance and run counts; teachers (name, email) and their sign-in keys; the Oracle settings, including the key if it was entered in the panel; Oracle usage records; competition entrants (a Telegram handle or an email), submissions and scores.data/also holds the link-signing key, the setup token, output images, class page files (page-files/), competition data and hidden answers, and package sets;workspace/<room>/holds room files, andbackups/the backups. An AI key set as a variable lives in/etc/colloq/colloq.env.- There is no retention period: data lives until the room or competition is deleted. Text deleted from a cell stays in the edit history, which participants can read by default. The browser gets a device cookie for one year.
What goes to the AI provider. When someone asks the Oracle: the room's notebooks in full (cells and output), the names of the room's files, the file the asker has open, and the question itself. Participants' names only when the Student names in model requests switch is on (panel → Oracle; on by default); otherwise the provider sees "Student 1", "Student 2" in the order people joined the room, and "Teacher" for hosts; the council uses labels S1…SN, which the teacher's console turns back into names. The switch can be turned off for the whole installation. Names that students wrote into their solutions themselves are sent either way. For sensitive courses, use a model on the internal network (Ollama, vLLM) or leave the Oracle off.
Competitions. On student-facing boards other people's email addresses are shortened (abc…@example.edu), and Telegram handles are shown as they are; entrants see their own row in full, staff see every row. In the competition editor, Who sees the leaderboard can keep the board to participants only. An entrant can be deleted from a competition, and their submissions leave the server's disk with them.
Audit log. The owner sees the panel's Audit log: staff sign-ins, changes to the staff list, deletions and settings (GET /api/admin/audit-log, owner only).
Staff access. The owner and teachers sign in with personal links, /admin/k/…; the owner can reissue a teacher's link, and the old one stops working at once. There is no SSO, LDAP or second-factor sign-in yet; the proxy can close off the panel instead — SSO or an address allowlist on /admin and /api/admin, paths students don't need. Any teacher can host any room, course and competition: one installation is one teaching team whose members trust each other.
Cookies and HTTPS. All cookies are HttpOnly and SameSite=Lax, and Secure behind an HTTPS proxy; for HSTS, see above.
Honest limits:
- no SSO;
- no per-room disk quota;
- a room is a shared workspace: participants share the kernel, the files, the Linux user and the personal-notebook container, so rooms don't suit individually graded or confidential work;
- a room link can't be revoked: whoever has it can join and, by default, run code;
- uploaded files are not scanned for malware;
- the interface font, HSE Sans, belongs to HSE University and is not covered by the MIT license; another university should replace it (THIRD_PARTY_NOTICES.md).
Kubernetes and single-node k3s
If your organisation already runs a Kubernetes cluster, install Colloq with the Helm chart into a namespace, as described in Colloq in your Kubernetes: a separate Pod per room, a private broker, network policies, images pinned by digest. The script that installs single-node k3s on a bare VM by itself (Installing on k3s) stays a preview for those without a cluster: no published release up to and including 0.9.0 carries its kit (release.json, colloq-deploy.tar.gz, SHA256SUMS), so it can't be installed from what has been published. Without a cluster, the recommended path is this page.