A new Teleport cluster¶
The Teleport master server page generates a .zip bundle for a self-hosted, single-node
Teleport 18.11.4 cluster (Auth + Proxy + application service in a single container).
Tenantry does not run it: you copy the bundle to the target host and launch it yourself.
The bundle runs the official Teleport image, under Teleport's license: see
Teleport licensing for MSPs.
Prerequisites¶
- Linux host with Docker and Docker Compose v2, clock synchronized (NTP).
- A fully qualified domain name (FQDN) for the cluster, e.g.
teleport.acme.ch: an IP will not do (WebAuthn requires a name). - Port 443 free and reachable by users (it is the only published port). With a Cloudflare Tunnel no inbound port is needed: you need a Cloudflare account holding the zone, with Zero Trust.
- Certificate, depending on the decision of the wizard (see TLS certificates):
reachable from the Internet: Teleport's built-in ACME (existing DNS A record, port 443 reachable
from the Internet, contact e-mail); not reachable: ACME DNS-01 through an acme.sh sidecar and your DNS
provider's API (no inbound port, optional wildcard
*.<cluster>for application access, the API token is asked for bybootstrap.shon the host, never in the bundle); your own ACME / PKI; your own certificates (PEM files incerts/) or self-signed (lab only: clients see certificate errors).
The wizard (4 steps)¶
- Cluster: FQDN, version (pinned, never
latest), certificate decision ("Is this server reachable from the Internet on port 80?" -> Teleport ACME, else DNS-01 with the provider picker and the wildcard option). - Administrator: name of the first user and their
logins, factors (WebAuthn / OTP). - Applications: built-in application service (dynamic registration) and, optionally,
Steward roles +
tenantryrole. - Preview and download: file list, warnings,
.zip.
The options Cloudflare Tunnel, Private address for tsh and agents and Install Tenantry on this server are described below.
Bundle contents: config/teleport.yaml, compose.yml, preflight.sh, bootstrap.sh,
backup.sh, README.md and, if requested, config/extra/roles-steward.yaml. With ACME DNS-01:
acme/issue.sh, acme/publish.sh, acme/provider.env (non-secret settings) and an acme service in
compose.yml; teleport.yaml uses https_keypairs with https_keypairs_reload_interval: 1h, so renewals need
no restart. ./bootstrap.sh [--dns-credentials-file FILE] asks for the DNS API token once (secrets/acme-dns.env, 0600).
WebAuthn and
rp_id. Therp_idequals the cluster FQDN. It is bound to the registered keys: changing the cluster or domain name invalidates them and forces all users to re-enroll. The cluster name cannot change after the first start: choose a final name.
Launching the deployment¶
On the target host, after extracting the .zip:
./preflight.sh # checks Docker, DNS, port 443, clock, certificates
./bootstrap.sh # preflight, docker compose up, wait for healthy state (180 s), first admin
bootstrap.sh displays the administrator's invitation link only once (valid for
1 hour, not stored): open it to set the password and enroll WebAuthn / OTP.
If the user already exists, the creation is skipped.
First sign-in¶
Open https://<FQDN>, sign in, then on the command line:
tsh login --proxy=<FQDN>:443 --user=<admin>
Cloudflare Tunnel (no inbound port)¶
Turn on Cloudflare Tunnel in the advanced options to keep port 443 closed. The bundle then contains a pinned cloudflared service that connects out to Cloudflare, and bootstrap.sh asks for the tunnel token (typed hidden, or --tunnel-token-file FILE). The token is stored only in secrets/cloudflared/tunnel-token (mode 0400).
- Teleport's built-in ACME needs port 443 from the Internet, so with a tunnel use ACME DNS-01, your own files or (lab) self-signed.
- The bundle README lists the public hostnames to create in the tunnel:
<cluster>(and*.<cluster>with the wildcard, for application access) tohttps://teleport:443with Match SNI to Host on; and Tenantry's name tohttp://tenantry:8443when it is installed with the cluster. - Private address for tsh and agents: tsh, agents and
tbotcannot log in to Cloudflare Access. Give the address of a WireGuard or NetBird interface and port 443 stays published on that address only. Without it, the generator warns you.
The whole picture (Access, Duo, the three options for non-browser clients): Teleport behind Cloudflare Tunnel + Access + Duo.
Installing Tenantry on the same server¶
With Install Tenantry on this server (public name, permission level), the bundle also contains tenantry/ (its installer). At the end of bootstrap.sh:
tenantry/scripts/install.sh --name <first label of the name> --public-url https://<tenantry name> \
--teleport-local docker:<teleport container> --bot-role minimal|full --version <x.y.z> \
--proxy <cluster>:443 --expose proxy --non-interactive
This creates the roles tenantry (minimal unless you chose full), steward-admin and steward-helpdesk and the Machine ID bot with the local tctl (role texts embedded in the installer, sent on stdin), installs the Tenantry release of the bundle (pinned by digest; bootstrap.sh stops when the bundle has no version), joins the compose network of Teleport, and prints the Tenantry administrator password once. Then:
- With a Cloudflare Tunnel: Tenantry is one more public hostname of the same tunnel (
http://tenantry:8443), behind its own Access application. Remember the Bypass application for/api/callbackand/api/i. - Without a tunnel: Tenantry is published as the Teleport application
tenantry(config/extra/tenantry-app.yaml, created bybootstrap.sh). You sign in to Teleport (with MFA), then openhttps://<tenantry name>. This needs the application service, and a certificate that covers the name: use ACME DNS-01 with the wildcard and a name such astenantry.<cluster>, or your own certificate files. Teleport's built-in ACME is refused. The DNS name must point to this host.
Upgrade Tenantry later (secrets and the bot identity are kept):
sudo tenantry/scripts/install.sh --name <name> --public-url https://<tenantry name> \
--teleport-local auto --expose proxy --version <x.y.z>
Giving Tenantry an identity and publishing Steward¶
Without "Install Tenantry on this server", the "Steward roles" option makes bootstrap.sh apply config/extra/roles-steward.yaml: the console roles (steward-admin, steward-helpdesk) and the tenantry role. The right to create roles is equivalent to administering the cluster: assign tenantry only to Tenantry's identity, and keep it short-lived.
- Install Tenantry on this server with
sudo scripts/install.sh --teleport-local auto(quick start), or from elsewhere with a bot (variants). - The node's built-in application service (
resourceswith the label*: *) serves any application created bytctl create: no separate agent needed. - In the Steward page, apply the integration, then follow Steward integration.
Updating¶
- Read the Teleport release notes (never skip a major version).
- Change the pinned image tag in
compose.yml. docker compose pull && docker compose up -d.
Also keep Tenantry's tctl version (--teleport-version) in line with the cluster.
Backup and restore¶
./backup.sharchivesconfig/anddata/(hot copy; stop the service for a copy guaranteed to be consistent).data/contains the certificate authority keys: mode 700, root access, encrypted backups.- Restore: stop the service (
docker compose down), restoreconfig/anddata/identically (same permissions), thendocker compose up -d. Test it before you need it.
Using tctl in the container¶
The image is distroless: no shell. Call the binary directly:
docker compose exec teleport /usr/local/bin/tctl --config=/etc/teleport/teleport.yaml status
Files passed to tctl create -f must come from a mounted directory: the bundle mounts
./config on /etc/teleport (so ./config/extra on /etc/teleport/extra). A docker cp
to /tmp (tmpfs) does not persist for the next command, and standard input does not
work: drop the YAML in ./config/extra/.
Troubleshooting¶
| Symptom | Cause | Action |
|---|---|---|
| Teleport shows "self-signed certificate", and the built-in application service logs reverse tunnel warnings | Self-signed mode: the certificate is not trusted, so the built-in application service does not validate the proxy. Expected. | Lab only. In production, use acme or files. |
preflight.sh: the FQDN does not resolve |
No DNS A record (blocking in ACME). | Create the record, wait for propagation. |
| ACME fails | 443 not reachable from the Internet, private name (.local, .internal, .lan), missing e-mail. |
Open 443, or switch to files / selfsigned for a private name. |
bootstrap.sh: not healthy after 180 s |
Configuration or certificate error. | docker compose logs --tail 100 teleport. |
exec ... sh / bash fails |
Distroless image without a shell. | Call /usr/local/bin/tctl --config=/etc/teleport/teleport.yaml ... directly (see above). |
tctl create -f: file not found |
File copied to the /tmp tmpfs or read from stdin. |
Put the file in ./config/extra/ (mounted at /etc/teleport/extra). |
| Cannot bind 443 / read a mounted volume | Capabilities removed. | The bundle keeps exactly two capabilities: NET_BIND_SERVICE (open 443 as root without the capability) and DAC_OVERRIDE (read/write volumes owned by another host user). Do not remove them; everything else is cap_drop: ALL, no-new-privileges. |
| Users must re-enroll (WebAuthn) | Cluster or domain name changed (rp_id). |
Go back to the original FQDN, otherwise a general re-enrollment is needed. |
Application created but missing from tctl apps ls |
Application service disabled in the bundle. | Regenerate with the built-in application service, or add an application agent. |
Tenantry tctl: access denied |
tenantry role not assigned to the identity, or identity expired. |
See Steward integration (a) and error codes. |
The container is not read-only: Teleport writes to /var/lib/teleport.
Only port 443 is published; restrict it with a firewall to the networks that need it.
Teleport licensing for MSPs: see License.