Skip to content

Front door & TLS

How a browser reaches the console and a workspace: one entry point, one door behind it, and the TLS you put in front.

Request tracesynthetic

Pick a request

  1. TLS edgetunnel or your proxy · 443tls ends

    Your tunnel or TLS proxy ends TLS and forwards to 127.0.0.1:5000.

  2. Console process:5000owner ok

    Session found, and this person owns acme/dev01. Forwarded over loopback with this workspace's own credential.

  3. Front doorCaddy · 127.0.0.1:8080route

    The route file for acme/dev01 matches and checks that credential.

  4. Workspacews-acme-dev01ttyd :7681

    The terminal answers.

https://kodela.example.com/ws/acme/dev01/200The terminal inside ws-acme-dev01.
handle /ws/acme/dev01/* -> ws-acme-dev01:7681 (ttyd)

Pick a request to see which process answers it and which rule refused it. The organisation acme, the workspaces and the addresses are examples; the rules and messages are the product's own.

What it is

The front door is a Caddy container, run by bin/front-door, that routes /ws/<organisation>/<workspace>/ to the right container: the terminal, the browser IDE or a preview of the developer's app. It sits behind the console process, which is the one entry point on port 5000. The console process checks the session and the owner first, then forwards to the door over loopback.

It is not what people type into their browser, and it does not give your users TLS. Kodela serves plain HTTP unless you put a TLS edge (a Cloudflare Tunnel or a reverse proxy you run) in front of port 5000. There is no automatic certificate.

Ports and binds

PortProcessDefault bindChange it
443your TLS edgeyoursnot part of Kodela
5000console processall addresses; the firewall admits loopback and Docker bridges onlyfirewall unit
8080front door127.0.0.1--bind ADDR or FRONT_DOOR_BIND; --port moves the host side
7681, 8443terminal, IDE inside each workspacethe workspace network onlynot published to the host

Workspaces are reached by container name on an internal Docker network, so nothing per workspace is published on the host and two organisations can both have a dev01. The door's port stays on loopback on purpose: a published container port is forwarded past the host firewall, so binding is the only thing that keeps the door from being reached around the console.

Door authentication: --auth

ModeDefaultWhat guards /ws/*Limit
workspaceyesone bcrypt verifier per workspace, written into that workspace's route file by workspace-agent create. The door holds no shared credential.a door with no route files serves nothing, which looks like a broken mount; status tells them apart
basicnoone shared user and password for every workspace (--password or FRONT_DOOR_PASSWORD)the door cannot tell two workspaces apart; anyone with the password reaches all of them
accessnoeach request is checked for a Cloudflare Access token and for ownership of the workspace, by a validator containerpartial: verified against a stand-in issuer only. Only for a topology where Access sits directly in front of the door; with the console process in the path it refuses every terminal. up refuses unless the validator is running and answering.

Leave it on workspace in the standard install. People are identified by the console process before the door sees them; the door's credential is between two of Kodela's own processes.

TLS options

OptionCoversHowLimit
TLS edge in front of port 5000 (recommended)everything: sign-in, console, API, terminals, IDE, previewsa Cloudflare Tunnel or your own reverse proxy, plus an https base URLyou run the certificate; no ACME in Kodela
Door internal CA: --tls internal/ws/* on the door onlyCaddy signs its own certificate; every client installs its roota stopgap for having no domain; the console on 5000 stays plain
Off: --tls offnothingthe default for a door that has never had TLSpasswords cross the network in the clear

An https origin needs two halves, and both must be on. The edge encrypts the connection; the console's public-TLS switch marks the session cookie Secure and sends HSTS. provision-host sets the switch from the scheme of --base-url; the committed serving-tier unit has it on. Without it the startup banner says PLAIN HTTP while everything still appears to work.

HSTS has no server-side undo. A browser that has seen it refuses http:// to that host for the whole max-age. Turn the switch on only when the https origin works.

The door's internal CA

on the host
$ cd ~/kodela && export AGENT_STACK_SHARED="$PWD/.agent-stack-data"
$ bin/front-door up --port 8080 --tls internal --tls-host kodela.example.test
$ bin/front-door ca --out kodela-door-root.crt
$ bin/front-door status

Install the printed root in every client's trust store, including the browser's: clicking through a warning does not cover the websocket the IDE needs. --tls-host is repeatable; the first becomes the default for clients that connect by IP address, which send no server name. The CA lives in a volume that survives down and up; removing it revokes every installed root. A door that already has TLS keeps it across up unless you say --tls off.

Cloudflare Tunnel and Access

A named tunnel is the recommended shape. The connector runs as a container on the door's network; the tunnel's public hostname, and any Cloudflare Access policy in front of it, live in your Cloudflare account.

on the host
$ read -rs FRONT_DOOR_TUNNEL_TOKEN && export FRONT_DOOR_TUNNEL_TOKEN
$ bin/front-door tunnel-up
# in Cloudflare: point kodela.example.com at http://203.0.113.10:5000
$ bin/front-door tunnel-down

tunnel-up --quick asks Cloudflare for a random, temporary hostname with no account (tunnel-url prints it), but it originates at the door, so it reaches /ws/* and not the console. Use it to test a workspace route, not to run a team.

The connector image is not pinned. The door's Caddy image is pinned by digest; cloudflared is not yet.

How workspaces and previews are routed

PathGoes toNotes
/admin/, /portal/, /api/the console processsessions, ownership and roles are decided here
/ws/acme/dev01/ws-acme-dev01:7681the terminal; owner only, and never an auditor
/ws/acme/dev01/ide/ws-acme-dev01:8443only for a workspace created with --ide; the prefix is stripped
/ws/acme/dev01/preview/<port>/ws-acme-dev01:<port>ports 1024 to 65535, never 7681 or 8443; owner-only unless an admin widens the port
/ws/<anything else>the door answers 404no such workspace, or it has no route on this host

workspace-agent create writes one route file per workspace into the state tree and reloads the door. The route file's authentication and its handlers are generated together, so a handled path is never an unauthenticated one.

Commands

bin/front-door up
Create or recreate the door. --port, --bind, --auth workspace|basic|access, --tls internal|off, --tls-host, --no-publish. Refuses unless AGENT_STACK_SHARED names the state tree.
bin/front-door reload
Pick up route files written since it started, without dropping websockets.
bin/front-door status
Ports, bind, TLS mode and who decided it, auth mode, and the routes Caddy has loaded compared with those on disk.
bin/front-door ca
The CA root clients must trust, under --tls internal.
bin/front-door tunnel-up / tunnel-url / tunnel-down
Run, read or remove the Cloudflare connector.
bin/front-door down
Remove the door. Workspaces keep running; their pages stop answering until it is back.

Verify it works

on the host
$ bin/front-door status
$ curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/ws/acme/dev01/
$ curl -sI https://kodela.example.com/admin/ | grep -i strict-transport-security
another machine on the network
$ curl -m 5 -s -o /dev/null -w '%{http_code}\n' http://203.0.113.10:5000/
$ curl -m 5 -s -o /dev/null -w '%{http_code}\n' http://203.0.113.10:8080/

Good looks like: status shows auth workspace, bind loopback and every route on disk loaded; the door answers 401 to a request with no credential; the https origin sends HSTS; and from the network both direct addresses print 000, a timeout or a refusal, never a page.

Troubleshooting

Every workspace answers 404

The route is on disk but not loaded, often because a reload could not use sudo. bin/front-door status compares loaded and on-disk routes; bin/front-door reload loads them. A 502 instead means loaded but stale: recreate the door with bin/front-door down, then up.

refusing to guess which routes to serve

Neither AGENT_STACK_SHARED nor WORKSPACE_ROUTES is set in this shell. Export the state tree the tier uses and retry.

--tls internal needs a reachable hostname

Pass --tls-host with a name or address clients use; loopback alone serves nobody.

Certificate name mismatch when opening by IP

Clients that connect by IP send no server name and only get the first --tls-host. Put the address first, or use a DNS name.

the validator is not running - refusing to flip the door onto it

--auth access needs the Access validator container up and answering first. In the standard install, stay on workspace.

The banner says PLAIN HTTP on an https origin

The public-TLS switch is off in the running unit. Re-provision with an https --base-url, or restore the committed unit's value and restart.

The IDE loads but never connects

Under the internal CA the root is not trusted by the browser, or your proxy is not passing websocket upgrades.

The console times out from the office network

By design: the firewall drops port 5000 from the network. Use the tunnel or your TLS proxy.

What it does not do yet

  • No ACME. Kodela does not obtain or renew certificates.
  • Plain HTTP by default. Until a TLS edge is in front, passwords and session cookies cross the network in the clear.
  • Internal CA covers the door only. It encrypts /ws/*, not the console on port 5000.
  • Access mode is partial. Verified against a stand-in issuer, and not compatible with the standard topology.
  • cloudflared is not pinned. The connector image tag moves.
  • Preview, IDE download and terminal copy are detectable, not preventable. Data can leave through a preview route a developer opens.
  • The shared front-door password is unattributable. Until “require identity” is turned on, a sign-in with it names nobody.

Verified against the main branch, 28 Sep 2026.