LCMD db logoLCMD[db]

Tailscale access

Reach the cluster and internal services over the tailnet — no SSH tunnel or EPFL VPN

How it works

The Tailscale Kubernetes operator runs in the cluster (tailscale namespace, installed by the tailscale-operator ArgoCD application) and joins our tailnet as a device named k8s. It does two things:

  • Proxies the Kubernetes API on the tailnet in auth mode: your Tailscale identity is impersonated into a Kubernetes group, then normal RBAC applies.
  • Exposes selected services as their own tailnet devices (via ingressClassName: tailscale or the tailscale.com/expose annotation), each with a MagicDNS hostname and an HTTPS certificate.

Access is governed by the tailnet ACL policy: group:lcmd members reach exposed services and get read-only kubectl (lcmd:viewersview); group:lcmd-admins get full kubectl (lcmd:cluster-adminscluster-admin). The group → cluster-role mapping lives in infrastructure/kubernetes/tailscale/rbac.yaml.

Joining the tailnet

Get invited

Ask an admin to add your account to group:lcmd (and group:lcmd-admins if you need write access to the cluster) in the tailnet ACL policy.

Install Tailscale

Install the Tailscale client on your machine and log in:

tailscale up

That's it — no VPN, no SSH keys.

Adding a team member (admins)

  1. Get a Google account email from them — EPFL addresses don't work on our tailnet.
  2. Users pageInvite external users → send the invite.
  3. In the ACL policy, add the email to group:lcmd (every member: exposed services + read-only kubectl). Add to group:lcmd-admins too only if they need full cluster access.
  4. Done — no tags involved. Tags (tag:k8s, tag:k8s-operator) belong to operator-created devices, never to people.
  5. Verify with them: Tailscale client logged in, then tailscale configure kubeconfig k8s && kubectl auth whoami shows their email and lcmd:viewers (or lcmd:cluster-admins).

Offboarding is the reverse: remove the email from the groups block, then delete the user from the Users page (their devices drop off the tailnet immediately).

The free plan caps the tailnet at 6 users. Check the Users page count before inviting.

kubectl over Tailscale

With Tailscale connected, one command writes a kubeconfig context pointing at the API-server proxy:

tailscale configure kubeconfig k8s
kubectl get pods -n prod

Your access level (read-only vs admin) is determined by your group in the tailnet ACL — there is no token to manage, and audit logs attribute requests to your Tailscale identity.

kubectl port-forward works through this context, so anything previously reachable only via the SSH tunnel (e.g. the ArgoCD UI) works over Tailscale too.

The SSH-tunnel setup described in Kubernetes access still works and remains the break-glass path if Tailscale is down.

Exposed services

ServiceURL
Django backend (admin UI)https://backend.<tailnet>.ts.net/admin/
ArgoCD UIhttps://argocd.<tailnet>.ts.net

Services are published to the tailnet with a Tailscale Ingress (one short hostname per service):

infrastructure/kubernetes/app/overlays/prod/tailscale-backend-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: backend-ts
spec:
  ingressClassName: tailscale
  defaultBackend:
    service:
      name: backend
      port:
        number: 8000
  tls:
    - hosts: [backend] # → https://backend.<tailnet>.ts.net

When exposing a new hostname that Django serves, also extend ALLOWED_HOSTS and CSRF_TRUSTED_ORIGINS in infrastructure/kubernetes/app/overlays/prod/patches/backend.yaml, or logins will fail with CSRF 403s.

The proxy forwards the whole hostname to one service (no path routing). New devices created this way are tagged tag:k8s and are reachable by group:lcmd per the ACL.

Devcontainers

No extra setup: local container runtimes (OrbStack, Docker Desktop) route outbound container traffic through the host's network stack, so the host's Tailscale connection — including MagicDNS and your identity — is shared with the devcontainer. The devcontainer's post-start.sh wires this automatically: it writes a tailscale-k8s kubeconfig context (placeholder token; the proxy authenticates you by tailnet identity) and makes it the default whenever the API proxy is reachable. Verify with kubectl auth whoami — it should print your Tailscale account and groups. If the ts.net hostname doesn't resolve or times out from inside the container (some Linux Docker setups), run your kubectl commands on the host instead; don't build a host-to-container tunnel. Containers on remote hosts (Codespaces, CI) have no host Tailscale to share — that case needs an in-container userspace tailscaled with a tagged ephemeral auth key; ask an admin.

Operator configuration

The install lives in GitOps:

The OAuth client (Tailscale admin console, scopes Devices Core + Auth Keys, tag tag:k8s-operator) is the only credential; rotating it means generating a new client and re-encrypting operator-oauth.enc.yaml.

On this page