# Topology Every VM is a Docker host. Proxmox and Cloud-Init only create the machine and bootstrap Ansible; service placement is controlled by inventory. ## Edge VM The edge VM runs: - Docker - Traefik - authentik IAM It publishes Traefik entrypoints on host ports `80` and `443`. authentik can run behind the same local Traefik instance by setting: ```yaml nbops_service_routing_mode: traefik authentik_domain: auth.example.invalid ``` ## Downstream VMs Downstream VMs also run Docker and normally run an internal Traefik instance. Application containers route through that local Traefik using Docker labels and the shared Docker proxy network. ```yaml nbops_roles: - base - ssh - ufw - updates - docker - traefik - gitea - immich nbops_service_routing_mode: traefik nbops_traefik_entrypoint: web nbops_traefik_tls: false ``` In this mode application roles do not publish their HTTP ports. They render Traefik labels and join `nbops_proxy_network`, which defaults to `proxy`. Only Traefik entrypoints should be exposed on the downstream host firewall. Use `traefik_bind_address` to bind those entrypoints to the VM's internal address. For central edge routing, the edge Traefik uses the file provider to route machine-to-machine traffic to each downstream VM's internal Traefik entrypoint. The internal Traefik then routes to applications using Docker labels. This keeps container discovery local to the VM and gives the edge-to-internal leg a stable place for future mTLS. Edge VM: ```yaml nbops_roles: - base - ssh - ufw - updates - docker - traefik traefik_docker_provider_enabled: false traefik_file_provider_enabled: true traefik_dynamic_inventory_group: nbops ``` Downstream VM: ```yaml nbops_service_routing_mode: traefik nbops_traefik_entrypoint: web nbops_traefik_tls: false traefik_bind_address: "10.0.10.25" traefik_publish_entrypoints: true nbops_traefik_file_routes: - name: jellyfin domain: jellyfin.example.invalid scheme: http port: 80 - name: gitea domain: git.example.invalid scheme: http port: 80 ``` When the play runs against the edge VM, Traefik renders `dynamic/http-services.yml` from `nbops_traefik_file_routes` declared across the inventory group. Each route targets `ansible_host` for that inventory host by default. Set `target_host` for a different VM address, or set `url` to provide the complete upstream URL. For future mTLS between edge Traefik and internal Traefik, routes can reference a Traefik `serversTransport`: ```yaml nbops_traefik_file_routes: - name: jellyfin domain: jellyfin.example.invalid scheme: https port: 443 servers_transport: internal-mtls traefik_dynamic_servers_transports: - name: internal-mtls server_name: apps-01.example.internal root_cas: - /etc/pki/internal/certs/ca.crt certificates: - cert_file: /etc/pki/internal/certs/edge.crt key_file: /etc/pki/internal/private/edge.key ``` ## Routing Modes Application roles support: - `host`: publish application ports directly and open those ports in UFW. - `traefik`: do not publish HTTP ports; attach to Traefik and render labels. - `traefik_file`: publish a service port without labels. This is available for exceptional direct edge-to-app routes, but the preferred multi-VM topology is edge Traefik -> internal Traefik -> application. Gitea is special: HTTP can be routed through Traefik while SSH remains published with `gitea_publish_ssh: true`.