tnl.dev :: docs

install a publisher.

Keep an Ubuntu or Compose application published with its own scoped identity.

keep an internal tool available

Publish a running Ubuntu application once and keep the same public URL after logout and reboot. The installed publisher has its own identity; visitors still need the saved URL's access policy and the application's own login.

Prerelease: this guide describes the implemented install/enrollment contract. Use a source-matched build containing it. Publication of these instructions waits for the combined server, SDK, and installed-runtime qualification; the currently published packages and images may not contain this workflow.

Start opencode on 127.0.0.1:4096, install the matching tnl binary with its legal files, and run as the operator who can manage team studio:

tnl publish http://127.0.0.1:4096 --name opencode --team studio --install --system

tnl uses your existing sign-in or asks you to sign in. It creates or reuses the saved public URL, preserves an existing visitor policy unless you explicitly change it, and enrolls a publisher identity with one exact public URL grant. New URLs default to restricted access. Review that policy before sharing; adding --anonymous explicitly removes tnl's visitor-credential requirement.

sudo authorizes copying the executable and legal files, writing private system state, and enabling tnl-publisher.service. Success reports the persistent public URL and current publish run only after a verified HTTP readiness request. The application's / must return 2xx. Readiness uses a short-lived, single-use proof bound to this run; it does not make the application anonymous.

The job captures the installing client's telemetry choice, including --no-telemetry or TNL_NO_TELEMETRY=1. Privileged registration and supervisor restarts preserve that choice rather than replacing it with the supervisor's preference.

inspect, restart, and update

sudo tnl service status opencode --system
tnl auth logout
sudo systemctl restart tnl-publisher.service
sudo tnl service status opencode --system

Systemd starts the publisher at boot. Supervise the application separately so it also starts at boot. The publisher's root-owned /var/lib/tnl/publisher directory is mode 0700 and retains its encrypted signing key, certificates, and runtime state. It contains no human control access or refresh token and does not depend on your home directory at boot. Keep the entire directory when backing up state; an exported signing key alone cannot restore certificate private keys.

Install a matching replacement binary and legal files, then repeat the original install command with the same target/name/team and intact system state to refresh the installed job. Inspect status and make a fresh authorized application request after an update or reboot. Each new publication has a new publish run number on the same saved URL. After an abrupt stop, control may need to expire the previous run's lease; the supervisor retries only while its runtime epoch remains current. Unavailable control cannot extend an expired authorization deadline.

Use sudo tnl service stop opencode --system to stop the job and sudo tnl service remove opencode --system to remove its installed job. Removing a supervisor is separate from revoking the publisher identity. Inspect tnl publisher list --team studio and revoke the exact retired identity with tnl publisher revoke PUBLISHER_ID --team studio when access should end.

run the publisher with compose

Use the maintained native compose.yaml and .env.example as an additional file in your application's Compose project. The equivalent reviewable example is also in this website repository's examples/compose-publisher. The application service must be named opencode, listen on 0.0.0.0:4096 inside its container, and share the default Compose network. No application port or Docker socket needs to be exposed to the publisher.

Create or select the saved URL from an operator machine:

tnl publish http://127.0.0.1:4096 --name opencode --team studio

Stop that foreground publication with Ctrl-C; the URL stays saved. Then:

tnl url inventory --team studio
tnl publisher enroll <public-url-id> --team studio --name opencode

Copy .env.example to a private mode-0600 .env outside source control:

inputvalue
TNL_IMAGEComplete immutable image@sha256:… reference for a release containing this workflow. No floating tag or invented digest.
TNL_SERVERExplicit HTTPS control origin for the enrollment.
TNL_TARGEThttp://opencode:4096, reachable from the publisher container.
TNL_ENROLL_KEYOne-use key printed by publisher enroll, scoped to this saved public URL.

With the additional file saved as compose.publisher.yaml:

docker compose --env-file .env -f compose.yaml -f compose.publisher.yaml up -d
docker compose --env-file .env -f compose.yaml -f compose.publisher.yaml logs publisher

The publisher runs foreground tnl publish --enrolled, as container user 0:0, with dropped capabilities and no-new-privileges. The publisher-state volume mounts at /var/lib/tnl; tnl creates its private 0700 state directory beneath it. restart: unless-stopped restarts the container when Docker starts. Allow its 45-second stop grace period to drain. Check logs for fresh readiness and make an authorized request; container running alone does not prove serving readiness.

After first enrollment succeeds and state is persisted, remove TNL_ENROLL_KEY from .env and recreate the publisher container. Ordinary restart, image update, and container replacement use the saved identity and volume, without a human login. Do not mount a person's client state. Each concurrent publisher needs its own enrolled identity and state; copying a volume does not authorize another owner.

recover lost state

A consumed enrollment key cannot recreate a deleted key. Missing state fails with TNL_CLIENT_PUBLISHER_ENROLLMENT_REQUIRED. Stop the old owner, identify and revoke its exact publisher identity, then enroll a replacement against the same saved public URL ID. Use a new private state directory or volume and the fresh key. The hostname stays the same; the replacement's run fences older runs. Do not restart an old container or restore a stale backup as a second writer.

Keep visitor policy, publisher permission, and application login separate. A publisher identity authorizes publication; it never grants visitor access. For remote service groups and AWS replicas, use publisher groups and AWS publishers.