You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[FEATURE] Docker label ACLs: read labels from the container list and cache them #1184
Is your feature request related to a problem? Please describe.
When ACLs come from Docker labels, every forward-auth request does a full uncached label lookup. DockerService.Lookup lists the containers, then calls ContainerInspect for each one until a container's tinyauth.apps.* labels match the domain (docker_service.go#L122-L154, v5.2.0). If no container matches, it inspects every container on the host needlessly.
On my host (34 containers, v5.2.0, Docker reached through a socket proxy via DOCKER_HOST), forward-auth latency changed in the following way once Docker access was enabled:
p50
p95
no Docker access (labels skipped)
0.042 ms
0.071 ms
Docker access, no matching labels
76 ms
84 ms
Each request to a protected app (page, scripts, images) goes through forward auth, so one page load can cause many of these lookups. Each lookup issues up to N+1 Docker API calls.
There is a second effect. Because labels are read through inspect, tinyauth needs access to GET /containers/{id}/json. That endpoint returns each container's full config, including its environment variables, which often hold secrets. That's why the socket-proxy allowlist in the access-controls docs (the one proposed in #618) has to allow containers/.*.
Describe the solution you'd like.
Read labels from the list response.ContainerList (GET /containers/json) already returns each container's Labels (container.Summary.Labels), so the per-container inspect isn't needed to decode tinyauth.apps.*. That makes a lookup one API call instead of up to N+1. tinyauth would then only need GET /containers/json and /_ping, and socket-proxy users could stop allowing inspect, which exposes secrets.
Cache the decoded apps. Build the label-to-app map once and refresh it on Docker events (GET /events filtered to type=container, on start, die and destroy), plus a periodic resync as a safety net. The Kubernetes provider already uses this watcher-and-resync pattern (kubernetes_service.go#L270-L335), so Docker would behave the same way. tinyauth already caches LDAP groups (LDAP_GROUPCACHETTL) and Tailscale lists (TAILSCALE_CACHEDURATION) for performance, and the documented socket-proxy allowlist already permits events.*, requiring no new proxy permissions.
(1) recovers most of the latency waste and removes the need for inspect. (2) makes label lookups only occur when a container changes.
Describe alternatives you've considered.
Static ACLs in config/env. These are checked before labels (GetAccessControls) and cost nothing, but a matching static app replaces label lookup for that domain entirely (no merging, as noted in feat: ACL labels from environment variables #422), so it doesn't seem like one can combine the two. Keeping ACLs next to each container's own definition is my main reason to use labels.
Adding labels to every protected app. The scan stops at the first match, so this lowers the average cost, but it still runs on every request, and hosts without labels still run a full scan.
Additional context
Related to #393: I think extending it such that a failed Docker connection at startup is logged at the warn level, not debug (docker_service.go#L47-L56), would also be of benefit for this feature. A cache can fail quietly: if the events stream drops, then ACLs go stale, and that likely should have the same warn-level visibility.
Human Written Confirmation
I confirm this request was written by me and not generated by an LLM or AI assistant.
Is your feature request related to a problem? Please describe.
When ACLs come from Docker labels, every forward-auth request does a full uncached label lookup.
DockerService.Lookuplists the containers, then callsContainerInspectfor each one until a container'stinyauth.apps.*labels match the domain (docker_service.go#L122-L154, v5.2.0). If no container matches, it inspects every container on the host needlessly.On my host (34 containers, v5.2.0, Docker reached through a socket proxy via
DOCKER_HOST), forward-auth latency changed in the following way once Docker access was enabled:Each request to a protected app (page, scripts, images) goes through forward auth, so one page load can cause many of these lookups. Each lookup issues up to N+1 Docker API calls.
There is a second effect. Because labels are read through inspect, tinyauth needs access to
GET /containers/{id}/json. That endpoint returns each container's full config, including its environment variables, which often hold secrets. That's why the socket-proxy allowlist in the access-controls docs (the one proposed in #618) has to allowcontainers/.*.Describe the solution you'd like.
ContainerList(GET /containers/json) already returns each container'sLabels(container.Summary.Labels), so the per-container inspect isn't needed to decodetinyauth.apps.*. That makes a lookup one API call instead of up to N+1. tinyauth would then only needGET /containers/jsonand/_ping, and socket-proxy users could stop allowing inspect, which exposes secrets.GET /eventsfiltered totype=container, on start, die and destroy), plus a periodic resync as a safety net. The Kubernetes provider already uses this watcher-and-resync pattern (kubernetes_service.go#L270-L335), so Docker would behave the same way. tinyauth already caches LDAP groups (LDAP_GROUPCACHETTL) and Tailscale lists (TAILSCALE_CACHEDURATION) for performance, and the documented socket-proxy allowlist already permitsevents.*, requiring no new proxy permissions.(1) recovers most of the latency waste and removes the need for inspect. (2) makes label lookups only occur when a container changes.
Describe alternatives you've considered.
GetAccessControls) and cost nothing, but a matching static app replaces label lookup for that domain entirely (no merging, as noted in feat: ACL labels from environment variables #422), so it doesn't seem like one can combine the two. Keeping ACLs next to each container's own definition is my main reason to use labels.Additional context
Related to #393: I think extending it such that a failed Docker connection at startup is logged at the warn level, not debug (docker_service.go#L47-L56), would also be of benefit for this feature. A cache can fail quietly: if the events stream drops, then ACLs go stale, and that likely should have the same warn-level visibility.
Human Written Confirmation