tinyauth v5.2.0 Docker Compose 部署指南

用 Docker Compose 单机部署 tinyauth v5.2.0 认证网关并完成 Traefik/Nginx/Caddy 三类反代 forward auth 集成,401→登录→200+Remote-User 全链路实测通过。

tinyauth自托管中间件2026/10/24

tinyauth v5.2.0 Docker Compose 部署指南

目标:用 Docker Compose 在单台 Ubuntu 22.04 服务器上部署 tinyauth v5.2.0 统一认证网关,并完成 Traefik、Nginx、Caddy 三类反向代理的 forward auth 集成,实现「未登录访问 → 302/401 跳登录页 → 登录后携带 Remote-User 等身份头回到应用」的完整认证链路。本文全部命令与回显已在 Docker Engine 29.8.1(Compose v5.5.1)环境实测通过。

一、环境信息

  • 操作系统:Ubuntu 22.04.4 LTS(x86_64)
  • Docker Engine:29.8.1,Docker Compose 插件 v5.5.1
  • tinyauth 版本:v5.2.0(2026-09-07 发布,官方镜像 ghcr.io/tinyauthapp/tinyauth)
  • 反向代理镜像:traefik:v3.5、nginx:1.29-alpine、caddy:2.10-alpine
  • 受保护示例应用:traefik/whoami:v1.10.4
  • 端口规划:tinyauth 服务宿主端口 8131(服务端口 3000);三套反代宿主端口 8135(Traefik)、8147(Caddy)、8149(Nginx)。生产环境建议反代直接监听 80/443,本文端口取值仅为与实测环境保持一致,可按需替换
  • 域名规划:auth.example.com(tinyauth 登录入口)、whoami.example.com(受保护应用示例)。两个均为 子域.主域.顶级域 三段式域名,这是 tinyauth 会话 Cookie 的硬性要求(见第十二章常见问题)
  • 编排目录:/opt/tinyauth/(示例路径,含 docker-compose.yml 与三个反代配置子目录)
  • 独立网络:tinyauth-net(bridge,网段 10.89.57.0/24)

二、镜像选型说明

tinyauth 官方只发布单一官方镜像,不提供多变体,选型要点集中在仓库地址与标签策略两点:

项目取值说明
官方镜像仓库ghcr.io/tinyauthapp/tinyauthGitHub Container Registry;项目仓库已由 steveiliop56/tinyauth 迁移为 tinyauthapp/tinyauth,旧路径镜像仍可拉取但新路径为官方现行地址
精确版本标签v5.2.0与 GitHub Release 一一对应,生产推荐,升级可控
浮动主版本标签v5跟随 v5.x 最新补丁,docker compose pull 即升级小版本
滚动预发布标签nightly滚动构建,含未发布改动,仅测试用

💡 说明:本文全部使用 v5.2.0 精确标签,保证文中回显可复现。镜像内以非 root 用户 tinyauth 运行,数据目录为 /data,服务监听 0.0.0.0:3000。

由于 tinyauth 本身只负责认证,必须搭配反向代理使用才能保护后端应用。三类主流反代的集成方式对比(本文三类全部实测,配置见第十章):

💡 技术拓展:反向代理接入 tinyauth 的三种主流方式——

反向代理接入机制配置位置特点
TraefikforwardAuth 中间件动态配置文件或标签原生 forwardAuth,tinyauth 官方默认集成对象;声明式配置
Nginxauth_request 子请求server/location 配置需要手动拼装 auth_request + error_page + 头透传三件套,步骤最多但最通用
Caddyforward_auth 指令Caddyfile语法最简,一行指令完成认证与头复制

三者最终都调用 tinyauth 暴露的对应端点:/api/auth/traefik、/api/auth/nginx、/api/auth/caddy(Nginx 端点为 v5.2.0 新增)。本文三类反代均实测部署并逐条验证,生产按既有技术栈选择即可。

三、前置条件

  1. 服务器已安装 Docker Engine 与 Compose 插件,验证:
docker version --format 'Engine {{.Server.Version}}'

实测输出:

Engine 29.8.1
docker compose version

实测输出:

Docker Compose version v5.5.1
  1. 域名解析就绪:auth.example.com 与 whoami.example.com 需能解析到服务器 IP。生产环境配置 DNS A 记录;本地验证环境可在执行验证命令的服务器 /etc/hosts 中加入:
127.0.0.1 auth.example.com whoami.example.com

⚠️ tinyauth 的会话 Cookie 写在主域(example.com)上,三套反代下的受保护应用才能共享登录态。因此两个域名必须是同一主域下的子域,且 TINYAUTH_APPURL 必须是 子域.主域.顶级域 三段式格式。裸域(如 example.com 本身)或两段式域名(如 auth.localtest)会导致受保护应用侧无法携带 Cookie,实测会直接拒绝访问(见第十二章常见问题 3)。

  1. 管理员账号的 bcrypt 哈希:不需要手工计算,tinyauth 自带 user create 命令生成(见第四章 4.1)。

四、部署步骤

本章按「创建网络 → 生成管理员账号 → 编写编排文件 → 启动 → 验证」的顺序完成 0→1 部署。预期结果:tinyauth 服务健康运行,登录页可访问,管理员可登录。

4.1 创建独立网络

为整套认证栈创建独立 bridge 网络并显式指定网段,避免与其他业务网段冲突:

docker network create -d bridge --subnet 10.89.57.0/24 tinyauth-net

实测输出:

01d9b1f9a3c4be0dcf7691ab0e39f0e2f83b0c1b8b8e0f6f9a1b2c3d4e5f6a7b8

💡 说明:显式 --subnet 便于在 TINYAUTH_AUTH_TRUSTEDPROXIES 中精确填写反代网段(见第十二章常见问题 5)。若网段与现有网络冲突,换一个空闲 /24 即可,同时同步修改编排文件与可信代理配置。

4.2 生成管理员账号

用 user create 命令生成 username:bcrypt_hash 格式的账号串。本文演示账号为 admin,密码 q1w2e3r4:

docker run -i -t --rm ghcr.io/tinyauthapp/tinyauth:v5.2.0 \
  user create --username admin --password q1w2e3r4 --docker

实测输出:

Created user 'admin'.

Environment variable:

TINYAUTH_AUTH_USERS=admin:$$2a$$10$$znGmVq.l0UmU91D7g5irQOwBY.HohGn8vNEahjCEgwnh/NzMWmWuC

CLI flags:

--auth.users=admin:$2a$10$znGmVq.l0UmU91D7g5irQOwBY.HohGn8vNEahjCEgwnh/NzMWmWuC

YAML config:

auth:
    users:
        - admin:$2a$10$znGmVq.l0UmU91D7g5irQOwBY.HohGn8vNEahjCEgwnh/NzMWmWuC

Use your config option of choice to add the user to Tinyauth and then restart.

💡 说明:

  • --docker 参数让输出的环境变量值把 bcrypt 哈希中的 $ 转义成 $$——这是 Compose 环境变量专属的转义规则;CLI flags 与 YAML config 两种形态保持单 $。按所用配置方式选取对应段落,不要混用。
  • bcrypt 每次生成使用随机盐,同一密码多次执行的哈希值不同且都有效,以自己实际执行结果为准。
  • 该命令只在本地计算哈希,不启动服务、不落库,输出的串需要填进编排文件后随服务生效。

4.3 编写编排文件

创建部署目录 /opt/tinyauth/,写入 docker-compose.yml。本文一套编排同时拉起 tinyauth、受保护示例应用 whoami 与三套反代,便于逐套验证:

mkdir -p /opt/tinyauth/{traefik,nginx/inc,caddy}

/opt/tinyauth/docker-compose.yml 完整内容:

name: tinyauth-v520-stack

networks:
  tinyauth-net:
    external: true

volumes:
  tinyauth-data:

services:
  tinyauth:
    image: ghcr.io/tinyauthapp/tinyauth:v5.2.0
    container_name: tinyauth-v5.2.0-app
    restart: unless-stopped
    ports:
      - "8131:3000"
    environment:
      - TINYAUTH_APPURL=http://auth.example.com
      - TINYAUTH_AUTH_USERS=admin:$$2a$$10$$Edf9Rb8EMGOjyXIf1.yLCur3593A9DlmuZyi5tTeT0./1dmWv62UK
      - TINYAUTH_AUTH_TRUSTEDPROXIES=10.89.57.0/24
    volumes:
      - tinyauth-data:/data
    networks:
      - tinyauth-net

  whoami:
    image: traefik/whoami:v1.10.4
    container_name: tinyauth-v5.2.0-whoami
    restart: unless-stopped
    networks:
      - tinyauth-net

  traefik:
    image: traefik:v3.5
    container_name: tinyauth-v5.2.0-traefik
    restart: unless-stopped
    ports:
      - "8135:80"
    volumes:
      - ./traefik:/etc/traefik/dynamic:ro
    command:
      - --providers.file.directory=/etc/traefik/dynamic
      - --entrypoints.web.address=:80
    networks:
      - tinyauth-net

  nginx:
    image: nginx:1.29-alpine
    container_name: tinyauth-v5.2.0-nginx
    restart: unless-stopped
    ports:
      - "8149:80"
    volumes:
      - ./nginx:/etc/nginx/conf.d:ro
      - ./nginx/inc:/etc/nginx/inc:ro
    networks:
      - tinyauth-net

  caddy:
    image: caddy:2.10-alpine
    container_name: tinyauth-v5.2.0-caddy
    restart: unless-stopped
    ports:
      - "8147:80"
    volumes:
      - ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro
    networks:
      - tinyauth-net

逐项说明关键配置:

配置项取值与作用
name: tinyauth-v520-stackCompose 项目名。多套编排共存时显式命名,避免 docker compose down 误删同名项目的其他资源;数据卷也会带上该前缀(见第五章)
networks.tinyauth-net.external: true复用 4.1 手工创建的网络,不随 down 删除
TINYAUTH_APPURL对外登录入口 URL,用于生成登录跳转地址与会话 Cookie 域。必须三段式域名,且与实际访问协议一致(HTTPS 部署时写 https://)
TINYAUTH_AUTH_USERS4.2 生成的账号串,$$ 为 Compose 转义形态
TINYAUTH_AUTH_TRUSTEDPROXIES可信反代网段;tinyauth 据此信任 X-Forwarded-* 头,取值与 4.1 网段一致
tinyauth-data:/data命名卷持久化数据库、OIDC 密钥等全部状态数据(见第五章)
反代三服务分别挂载各自配置目录,配置内容见第十章

4.4 启动与验证

先写入第十章的三个反代配置文件,再启动整套编排:

cd /opt/tinyauth && docker compose up -d

实测输出(首次启动节选):

 Network tinyauth-v520-stack_default Creating
 Network tinyauth-v520-stack_default Created
 Container tinyauth-v5.2.0-traefik Creating
 Container tinyauth-v5.2.0-nginx Creating
 Container tinyauth-v5.2.0-caddy Creating
 Container tinyauth-v5.2.0-whoami Creating
 Container tinyauth-v5.2.0-app Creating
 Container tinyauth-v5.2.0-app Created
 Container tinyauth-v5.2.0-whoami Created
 Container tinyauth-v5.2.0-caddy Created
 Container tinyauth-v5.2.0-nginx Created
 Container tinyauth-v5.2.0-traefik Created
 Container tinyauth-v5.2.0-app Starting
 Container tinyauth-v5.2.0-whoami Starting
 Container tinyauth-v5.2.0-traefik Starting
 Container tinyauth-v5.2.0-nginx Starting
 Container tinyauth-v5.2.0-caddy Starting
 Container tinyauth-v5.2.0-app Started
 Container tinyauth-v5.2.0-whoami Started
 Container tinyauth-v5.2.0-traefik Started
 Container tinyauth-v5.2.0-nginx Started
 Container tinyauth-v5.2.0-caddy Started

确认全部服务运行状态(tinyauth 带内置健康检查,约 30 秒内转为 healthy):

docker compose ps

实测输出:

NAME                       IMAGE                             COMMAND                  SERVICE    CREATED              STATUS                            PORTS
tinyauth-v5.2.0-app        ghcr.io/tinyauthapp/tinyauth:v5.2.0   "/app/tinyauth"      tinyauth   About a minute ago   Up About a minute (healthy)       0.0.0.0:8131->3000/tcp, [::]:8131->3000/tcp
tinyauth-v5.2.0-caddy      caddy:2.10-alpine                 "caddy run --config …"   caddy      About a minute ago   Up About a minute                 0.0.0.0:8147->80/tcp, [::]:8147->80/tcp
tinyauth-v5.2.0-nginx      nginx:1.29-alpine                 "/docker-entrypoint.…"   nginx      About a minute ago   Up About a minute                 0.0.0.0:8149->80/tcp, [::]:8149->80/tcp
tinyauth-v5.2.0-traefik    traefik:v3.5                      "entrypoint.sh --pro…"   traefik    About a minute ago   Up About a minute                 0.0.0.0:8135->80/tcp, [::]:8135->80/tcp
tinyauth-v5.2.0-whoami     traefik/whoami:v1.10.4            "/whoami"                whoami     About a minute ago   Up About a minute                 80/tcp

验证 1:登录页直连可访问(HTTP 200 即服务就绪):

curl -sI http://127.0.0.1:8131/ | head -4

实测输出:

HTTP/1.1 200 OK
Accept-Ranges: bytes
Cache-Control: public, max-age=900
Content-Type: text/html; charset=utf-8

验证 2:用 tinyauth healthcheck 命令做应用层健康确认:

docker exec tinyauth-v5.2.0-app tinyauth healthcheck

实测输出:

2026-10-23T21:23:16Z INF Performing health check app_url=http://127.0.0.1:3000 stream=app
2026-10-23T21:23:16Z INF Tinyauth is healthy response={"message":"Healthy","status":200} stream=app

验证 3:确认版本无误:

docker exec tinyauth-v5.2.0-app tinyauth version

实测输出:

Version: v5.2.0
Commit Hash: 653b747829cc72b5ad5232db63b075fb66f81796
Build Timestamp: 2026-09-07T17:15:08

三步验证全部通过,tinyauth 服务本体部署完成。反向代理集成与端到端认证链路验证见第十章。

五、数据持久化说明

tinyauth 把全部状态数据集中在 /data 目录,编排中已用命名卷 tinyauth-data 挂载。查看卷内数据:

docker exec tinyauth-v5.2.0-app ls -la /data/

实测输出:

total 72
drwxr-xr-x    5 tinyauth tinyauth      4096 Oct 23 21:30 .
drwxr-xr-x    1 root     root          4096 Oct 23 21:30 ..
drwxr-xr-x    2 tinyauth tinyauth      4096 Sep  7 17:16 oidc
drwxr-xr-x    2 tinyauth tinyauth      4096 Sep  7 17:16 resources
drwxr-xr-x    2 tinyauth tinyauth      4096 Sep  7 17:16 tailscale
-rw-r--r--    1 root     root         53248 Oct 23 21:30 tinyauth.db
路径内容
tinyauth.dbSQLite 主数据库(默认驱动),存放用户、会话等数据
oidc/OIDC 提供方功能的 RSA 私钥/公钥(key.pem、key.pub),首次启用 OIDC 时生成
resources/资源服务器目录(自定义背景图等 UI 资源)
tailscale/Tailscale 集成缓存数据

命名卷在宿主机上的实际名称带 Compose 项目前缀,可确认:

docker volume ls --filter name=tinyauth

实测输出:

DRIVER    VOLUME NAME
local     tinyauth-v520-stack_tinyauth-data

重启服务后验证持久化效果:账号数据完整保留(重新登录成功),旧会话 Cookie 失效需重新登录:

docker restart tinyauth-v5.2.0-app

实测输出:

tinyauth-v5.2.0-app

重启后用重启前的旧 Cookie 访问受保护应用,返回 401(会话已失效):

HTTP/1.1 401 Unauthorized
Content-Length: 39
Content-Type: application/json; charset=utf-8
X-Tinyauth-Location: http://auth.example.com/login?login_for=app&redirect_uri=http%3A%2F%2Fwhoami.example.com%3A8135%2F

{"message":"Unauthorized","status":401}

重新登录(携带正确账号)返回 200,账号与配置均从数据卷恢复:

HTTP/1.1 200 OK
Content-Length: 43
Content-Type: application/json; charset=utf-8
Set-Cookie: tinyauth-session-26742baf=684cad9b-ed07-48ce-b3c4-37b64cade101; Path=/; Domain=example.com; Expires=Sat, 24 Oct 2026 21:31:04 GMT; Max-Age=86399; HttpOnly; SameSite=Lax

{"message":"Login successful","status":200}

💡 说明:备份即备份该命名卷(或直接备份卷内 tinyauth.db);若改用 PostgreSQL 作为外部数据库(TINYAUTH_DATABASE_DRIVER=postgres),备份对象转为对应数据库(参数说明见第七章)。

六、环境变量表

tinyauth 支持环境变量与 CLI 标志两种等价配置方式,环境变量命名规则为 TINYAUTH_ 前缀 + 分层参数名大写下划线(如标志 --auth.sessionexpiry 对应 TINYAUTH_AUTH_SESSIONEXPIRY)。下表列出部署阶段最常用的一批(全量参数与分组详解见配置参数详解篇):

环境变量默认值说明
TINYAUTH_APPURL空对外访问基础 URL。必配;决定登录跳转地址与会话 Cookie 域,须三段式域名
TINYAUTH_AUTH_USERS空用户列表,逗号分隔的 username:bcrypt_hash[:totp_secret]
TINYAUTH_AUTH_USERSFILE空用户文件路径,替代环境变量内联用户列表
TINYAUTH_SERVER_PORT3000服务监听端口
TINYAUTH_SERVER_ADDRESS0.0.0.0服务监听地址
TINYAUTH_DATABASE_DRIVERsqlite数据库驱动,可选 sqlite、postgres、memory
TINYAUTH_DATABASE_PATH发行形态 /data/tinyauth.dbSQLite 文件路径;驱动为 postgres 时此值为连接 URL
TINYAUTH_AUTH_TRUSTEDPROXIES空可信反代地址/CIDR 列表,逗号分隔
TINYAUTH_AUTH_SESSIONEXPIRY86400会话有效期(秒),实测设 120 时 Cookie Max-Age=119
TINYAUTH_AUTH_SESSIONMAXLIFETIME0会话最长存活时间(秒),0 表示不限
TINYAUTH_AUTH_SECURECOOKIEfalseCookie 仅经 HTTPS 传输;生产 HTTPS 部署应设 true
TINYAUTH_AUTH_SUBDOMAINSENABLEDtrue子域共享登录态开关
TINYAUTH_AUTH_LOGINTIMEOUT300登录流程超时(秒)
TINYAUTH_AUTH_LOGINMAXRETRIES3最大登录重试次数
TINYAUTH_APPS_<NAME>_CONFIG_DOMAIN空应用 ACL:应用 <NAME> 的域名
TINYAUTH_APPS_<NAME>_USERS_ALLOW空应用 ACL:允许的用户列表(逗号分隔)
TINYAUTH_APPS_<NAME>_USERS_BLOCK空应用 ACL:拒绝的用户列表
TINYAUTH_UI_TITLETinyauth登录页标题
TINYAUTH_CONFIGFILE空YAML 配置文件路径(见第八章)

💡 说明:Compose 中写 bcrypt 哈希必须把 $ 写成 $$(Compose 层转义),漏转义是「登录一直失败」的高频原因(见第十二章常见问题 6)。多用户写法(逗号分隔,示例中两段哈希均为实测生成):TINYAUTH_AUTH_USERS=admin:$$2a$$10$$Edf9Rb8EMGOjyXIf1.yLCur3593A9DlmuZyi5tTeT0./1dmWv62UK,user2:$$2a$$10$$znGmVq.l0UmU91D7g5irQOwBY.HohGn8vNEahjCEgwnh/NzMWmWuC。

七、配置参数说明

第四章编排文件中的三个环境变量是「能跑起来」的最小集,逐项展开:

  • TINYAUTH_APPURL=http://auth.example.com:tinyauth 以此推导会话 Cookie 的作用域(主域 example.com)与登录跳转地址。未认证访问受保护应用时,反代收到的跳转地址就来自它(实测跳转 X-Tinyauth-Location: http://auth.example.com/login?login_for=app&redirect_uri=http%3A%2F%2Fwhoami.example.com%3A8135%2F)。HTTPS 上线后同步改为 https://auth.example.com 并开启 TINYAUTH_AUTH_SECURECOOKIE=true。
  • TINYAUTH_AUTH_USERS=admin:$$2a$$10$$Edf9Rb8EMGOjyXIf1.yLCur3593A9DlmuZyi5tTeT0./1dmWv62UK:本地账号体系的唯一定义处。账号密码不落 tinyauth 数据库,而是随环境变量注入;修改账号 = 改环境变量 + 重启服务。
  • TINYAUTH_AUTH_TRUSTEDPROXIES=10.89.57.0/24:把三套反代所在网段声明为可信来源,tinyauth 才采信请求里的 X-Forwarded-For 等头并回写正确的跳转地址。实测配置转储可见其已生效:
docker exec tinyauth-v5.2.0-app tinyauth config | grep -A 1 trustedProxies

实测输出(ANSI 着色符已剥离):

    trustedProxies:
        - 10.89.57.0/24

进一步需要关注的分档:最小可用(上表三个变量);生产 HTTPS(追加 SECURECOOKIE=true、反代侧启用 TLS);按应用管控(追加 TINYAUTH_APPS_<NAME>_* 系列,实现「登录了也不能访问指定应用」的应用级 ACL,实测验证见第十二章常见问题 7)。完整参数矩阵、取值细节与生产配置模板见参数详解篇《tinyauth v5.2.0 配置参数详解》。

八、配置文件挂载

除环境变量外,tinyauth 支持从 YAML 配置文件读取全部参数,适合参数多、需要纳入配置管理的场景。用法两步:

  1. 编写配置文件 /opt/tinyauth/config.yaml(键名为驼峰式分层结构,与 tinyauth config 命令转储出的格式一致):
appUrl: http://auth.example.com
server:
  port: 3000
  address: 0.0.0.0
auth:
  users:
    - admin:$2a$10$Edf9Rb8EMGOjyXIf1.yLCur3593A9DlmuZyi5tTeT0./1dmWv62UK
  trustedProxies:
    - 10.89.57.0/24
  sessionExpiry: 86400
database:
  driver: sqlite
  path: /data/tinyauth.db

⚠️ YAML 文件形态下 bcrypt 哈希保持单 $,不做 $$ 转义(转义仅适用于 Compose 环境变量)。

  1. 挂载并在编排中指定路径:
services:
  tinyauth:
    environment:
      - TINYAUTH_CONFIGFILE=/data/config.yaml
    volumes:
      - ./config.yaml:/data/config.yaml:ro
      - tinyauth-data:/data

修改配置文件后重启服务生效:

docker compose restart tinyauth

实测输出:

[+] Restarting 1/1
 ✔ Container tinyauth-v5.2.0-app  Started

💡 说明:三种配置途径(环境变量 / CLI 标志 / YAML 文件)内容完全等价,tinyauth config 命令会把当前生效的合并结果以 YAML 转储出来,是排查「配置到底生效没有」的第一工具。建议同一参数只走一种途径,避免多来源歧义。

九、等价 docker run

不用 Compose 时,以下单条 docker run 与第四章中 tinyauth 服务的编排等价(网络与数据卷复用同一套):

docker run -d --name tinyauth-app --network tinyauth-net -p 8131:3000 \
  -e TINYAUTH_APPURL=http://auth.example.com \
  -e 'TINYAUTH_AUTH_USERS=admin:$2a$10$Edf9Rb8EMGOjyXIf1.yLCur3593A9DlmuZyi5tTeT0./1dmWv62UK' \
  -v tinyauth-data:/data \
  ghcr.io/tinyauthapp/tinyauth:v5.2.0

⚠️ docker run 直跑时 -e 传参不做 Compose 的 $$ 转义,bcrypt 哈希保持单 $(与 Compose 相反),并建议像上例一样给变量值加单引号,防止 shell 把 $2a 展开成位置参数。

该形态已在同环境实测:实例正常启动并转为健康状态,登录页返回 200,tinyauth version 回显 Version: v5.2.0,与 Compose 形态行为一致。

十、三类反向代理集成实测

本章把 Traefik、Nginx、Caddy 三套反代逐一接到 tinyauth 上,保护同一个 whoami 应用,并对每套完成「未认证 → 登录 → 已认证」全链路实测。三套配置目录已挂载进对应反代(见第四章编排文件)。

10.1 Traefik 集成(forwardAuth 中间件)

/opt/tinyauth/traefik/dynamic.yml:

http:
  routers:
    tinyauth:
      rule: "Host(`auth.example.com`)"
      service: tinyauth-svc
      entryPoints:
        - web
    whoami:
      rule: "Host(`whoami.example.com`)"
      service: whoami-svc
      entryPoints:
        - web
      middlewares:
        - tinyauth-fw

  middlewares:
    tinyauth-fw:
      forwardAuth:
        address: "http://tinyauth:3000/api/auth/traefik"
        authResponseHeaders:
          - Remote-User
          - Remote-Name
          - Remote-Email
          - Remote-Groups

  services:
    tinyauth-svc:
      loadBalancer:
        servers:
          - url: "http://tinyauth:3000"
    whoami-svc:
      loadBalancer:
        servers:
          - url: "http://whoami:80"

配置要点:tinyauth 路由把登录入口反代到 tinyauth;whoami 路由挂 tinyauth-fw 中间件,forwardAuth.address 指向 tinyauth 的 Traefik 专用端点 /api/auth/traefik;authResponseHeaders 声明认证成功后要从 tinyauth 响应复制给后端应用的四个身份头。

10.2 Nginx 集成(auth_request 子请求)

/opt/tinyauth/nginx/default.conf:

server {
    listen 80;
    server_name auth.example.com;

    location / {
        proxy_pass http://tinyauth:3000;
        proxy_set_header Host $host;
        proxy_set_header x-forwarded-for $proxy_add_x_forwarded_for;
        proxy_set_header x-real-ip $remote_addr;
    }
}

server {
    listen 80;
    server_name whoami.example.com;

    include /etc/nginx/inc/tinyauth-server.conf;

    location / {
        include /etc/nginx/inc/tinyauth-location.conf;
        proxy_pass http://whoami:80;
    }
}

/opt/tinyauth/nginx/inc/tinyauth-location.conf(认证子请求定义):

location /tinyauth {
    internal;
    resolver 127.0.0.11;
    set $upstream_tinyauth tinyauth;
    proxy_pass http://$upstream_tinyauth:3000/api/auth/nginx;
    proxy_pass_request_body off;
    proxy_set_header content-length "";
    proxy_set_header x-original-url $scheme://$host$request_uri;
    proxy_set_header x-original-method $request_method;
    proxy_set_header x-forwarded-for $proxy_add_x_forwarded_for;
    proxy_set_header x-real-ip $remote_addr;
}

/opt/tinyauth/nginx/inc/tinyauth-server.conf(认证挂接与身份头透传):

auth_request /tinyauth;
auth_request_set $redirection_url $upstream_http_x_tinyauth_location;
error_page 401 403 =302 $redirection_url;
auth_request_set $email $upstream_http_remote_email;
auth_request_set $groups $upstream_http_remote_groups;
auth_request_set $name $upstream_http_remote_name;
auth_request_set $user $upstream_http_remote_user;
proxy_set_header remote-email $email;
proxy_set_header remote-groups $groups;
proxy_set_header remote-name $name;
proxy_set_header remote-user $user;

配置要点:Nginx 走 auth_request 子请求模式——location /tinyauth(internal,仅内部可用)把每个请求转成对 tinyauth 端点 /api/auth/nginx 的子请求,并回传原始 URL 与方法(x-original-url / x-original-method);子请求返回 401/403 时由 error_page 401 403 =302 $redirection_url 重定向到 tinyauth 返回的登录地址(x-tinyauth-location 响应头,经 auth_request_set 捕获);认证通过后把四个 remote-* 身份头透传给后端。

10.3 Caddy 集成(forward_auth 指令)

/opt/tinyauth/caddy/Caddyfile:

{
	admin off
	auto_https off
}

auth.example.com:80 {
	reverse_proxy tinyauth:3000
}

whoami.example.com:80 {
	forward_auth tinyauth:3000 {
		uri /api/auth/caddy
		copy_headers Remote-User Remote-Name Remote-Email Remote-Groups
	}
	reverse_proxy whoami:80
}

配置要点:forward_auth 是 Caddy 对认证子请求的封装,uri 指向 tinyauth 的 Caddy 专用端点 /api/auth/caddy,copy_headers 把认证成功响应中的四个身份头复制给后端请求。语法量最少,等价于 Nginx 的一整段 auth_request 拼装。

10.4 端到端认证链路实测(三反代逐一验证)

验证方法:curl --resolve 把两个域名指向反代端口,模拟浏览器访问。三步走:未认证访问(应跳登录)→ 调登录 API 拿会话 Cookie → 带 Cookie 再访问(应 200 且拿到身份头)。

第 1 步:未认证访问。 Traefik(8135)返回 401 并在 X-Tinyauth-Location 头给出登录跳转地址:

curl -sI --resolve whoami.example.com:8135:127.0.0.1 http://whoami.example.com:8135/

实测输出:

HTTP/1.1 401 Unauthorized
Content-Length: 39
Content-Type: application/json; charset=utf-8
X-Tinyauth-Location: http://auth.example.com/login?login_for=app&redirect_uri=http%3A%2F%2Fwhoami.example.com%3A8135%2F

{"message":"Unauthorized","status":401}

Nginx(8149)返回 302 直接跳登录页(error_page 401 =302 的效果):

HTTP/1.1 302 Moved Temporarily
Server: nginx/1.29.8
Location: http://auth.example.com/login?login_for=app&redirect_uri=http%3A%2F%2Fwhoami.example.com%2F

<html>
<head><title>302 Found</title></head>
<body>
<center><h1>302 Found</h1></center>
<hr><center>nginx/1.29.8</center>
</body>

Caddy(8147)与 Traefik 同型,401 + X-Tinyauth-Location(另带 Via: 1.1 Caddy 头):

HTTP/1.1 401 Unauthorized
Content-Length: 39
Content-Type: application/json; charset=utf-8
Via: 1.1 Caddy
X-Tinyauth-Location: http://auth.example.com/login?login_for=app&redirect_uri=http%3A%2F%2Fwhoami.example.com%3A8147%2F

{"message":"Unauthorized","status":401}

💡 说明:三种反代跳转形态不同(Traefik/Caddy 透传 401 由前端处理跳转,Nginx 服务端 302),但登录地址都指向 auth.example.com/login 且带 redirect_uri 回跳参数,浏览器访问体验一致。

第 2 步:登录拿 Cookie。 调 tinyauth 登录 API(POST /api/user/login,JSON 体),经登录入口域名访问,Cookie 写入 example.com 主域:

curl -s --resolve auth.example.com:8131:127.0.0.1 \
  -c /tmp/tinyauth-cookies.txt \
  -H 'Content-Type: application/json' \
  -d '{"username":"admin","password":"q1w2e3r4"}' \
  -i http://auth.example.com:8131/api/user/login

实测输出:

HTTP/1.1 200 OK
Content-Length: 43
Content-Type: application/json; charset=utf-8
Set-Cookie: tinyauth-session-26742baf=aac628e6-e7d3-4fa9-9db4-176bed051667; Path=/; Domain=example.com; Expires=Sat, 24 Oct 2026 21:29:27 GMT; Max-Age=86399; HttpOnly; SameSite=Lax

{"message":"Login successful","status":200}

Cookie 文件内容(会话 Cookie 已落在主域):

# Netscape HTTP Cookie File
# https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.

#HttpOnly_.example.com	TRUE	/	FALSE	1792877366	tinyauth-session-26742baf	aac628e6-e7d3-4fa9-9db4-176bed051667

第 3 步:带 Cookie 访问三套反代。 全部返回 200,且后端 whoami 回显中都能看到 tinyauth 注入的三个身份头(Remote-User: admin、Remote-Name: Admin、Remote-Email: admin@example.com):

Traefik(8135):

curl -s --resolve whoami.example.com:8135:127.0.0.1 \
  -b /tmp/tinyauth-cookies.txt http://whoami.example.com:8135/

实测输出:

Hostname: b1421f51062f
IP: 127.0.0.1
IP: ::1
IP: 10.89.57.5
RemoteAddr: 10.89.57.2:45592
GET / HTTP/1.1
Host: whoami.example.com:8135
User-Agent: curl/7.81.0
Accept: */*
Accept-Encoding: gzip
Cookie: tinyauth-session-26742baf=aac628e6-e7d3-4fa9-9db4-176bed051667
Remote-Email: admin@example.com
Remote-Name: Admin
Remote-User: admin
X-Forwarded-For: 10.89.57.1
X-Forwarded-Host: whoami.example.com:8135
X-Forwarded-Port: 8135
X-Forwarded-Proto: http
X-Forwarded-Server: f3e9025d43d7
X-Real-Ip: 10.89.57.1

Nginx(8149):

curl -s --resolve whoami.example.com:8149:127.0.0.1 \
  -b /tmp/tinyauth-cookies.txt http://whoami.example.com:8149/

实测输出:

Hostname: b1421f51062f
IP: 127.0.0.1
IP: ::1
IP: 10.89.57.5
RemoteAddr: 10.89.57.3:57500
GET / HTTP/1.1
Host: whoami
User-Agent: curl/7.81.0
Accept: */*
Cookie: tinyauth-session-26742baf=aac628e6-e7d3-4fa9-9db4-176bed051667
Remote-Email: admin@example.com
Remote-Name: Admin
Remote-User: admin

Caddy(8147):

curl -s --resolve whoami.example.com:8147:127.0.0.1 \
  -b /tmp/tinyauth-cookies.txt http://whoami.example.com:8147/

实测输出:

Hostname: b1421f51062f
IP: 127.0.0.1
IP: ::1
IP: 10.89.57.5
RemoteAddr: 10.89.57.6:42106
GET / HTTP/1.1
Host: whoami.example.com:8147
User-Agent: curl/7.81.0
Accept: */*
Accept-Encoding: gzip
Cookie: tinyauth-session-26742baf=aac628e6-e7d3-4fa9-9db4-176bed051667
Remote-Email: admin@example.com
Remote-Name: Admin
Remote-User: admin
Via: 1.1 Caddy
X-Forwarded-For: 10.89.57.1
X-Forwarded-Host: whoami.example.com:8147
X-Forwarded-Proto: http

三套反代全链路验证结论:

反向代理未认证登录后身份头透传
Traefik v3.5401 + X-Tinyauth-Location200Remote-User/Name/Email 齐全
Nginx 1.29302 跳登录页200Remote-User/Name/Email 齐全
Caddy 2.10401 + X-Tinyauth-Location200Remote-User/Name/Email 齐全

10.5 Basic 认证与补充说明

tinyauth 同时支持 HTTP Basic 认证直打反代(适合脚本、命令行工具场景),凭据为 tinyauth 用户账号:

curl -s --resolve whoami.example.com:8135:127.0.0.1 -u admin:q1w2e3r4 http://whoami.example.com:8135/ | grep -E 'Remote-|Authorization'

实测输出:

Authorization: Basic YWRtaW46cTF3MmUzcjQ=
Remote-Email: admin@example.com
Remote-Name: Admin
Remote-User: admin

密码错误时返回 401 并给出登录跳转地址:

HTTP/1.1 401 Unauthorized
Content-Length: 39
Content-Type: application/json; charset=utf-8
X-Tinyauth-Location: http://auth.example.com/login?login_for=app&redirect_uri=http%3A%2F%2Fwhoami.example.com%3A8135%2F

{"message":"Unauthorized","status":401}

💡 说明:官方文档另提供 Nginx Proxy Manager 与 SWAG(linuxserver 反代镜像)两条图形化/模板化集成路线,以及 Kubernetes 与标签(labels)驱动的自动 ACL 方式;本文三套为手工配置的反代典型代表,覆盖了 traefik/nginx/caddy 三个专用端点的用法,其余路线可在官方文档 Guides 分区按既有技术栈选用。

十一、常用运维命令

在编排目录 /opt/tinyauth/ 内执行:

# 查看服务状态(healthy 即正常)
docker compose ps

实测输出(稳定运行期):

NAME                       IMAGE                             COMMAND                  SERVICE    CREATED              STATUS                 PORTS
tinyauth-v5.2.0-app        ghcr.io/tinyauthapp/tinyauth:v5.2.0   "/app/tinyauth"      tinyauth   7 hours ago          Up 7 hours (healthy)   0.0.0.0:8131->3000/tcp, [::]:8131->3000/tcp
tinyauth-v5.2.0-caddy      caddy:2.10-alpine                 "caddy run --config …"   caddy      7 hours ago          Up 7 hours             0.0.0.0:8147->80/tcp, [::]:8147->80/tcp
tinyauth-v5.2.0-nginx      nginx:1.29-alpine                 "/docker-entrypoint.…"   nginx      7 hours ago          Up 7 hours             0.0.0.0:8149->80/tcp, [::]:8149->80/tcp
tinyauth-v5.2.0-traefik    traefik:v3.5                      "entrypoint.sh --pro…"   traefik    7 hours ago          Up 7 hours             0.0.0.0:8135->80/tcp, [::]:8135->80/tcp
tinyauth-v5.2.0-whoami     traefik/whoami:v1.10.4            "/whoami"                whoami     7 hours ago          Up 7 hours             80/tcp
# 跟踪认证日志(登录成功/失败、forward auth 校验都在这里)
docker compose logs -f tinyauth

实测输出节选(一次完整的「未认证 401 → 登录成功 → 携 Cookie 校验 200」):

2026-10-23T21:24:52Z INF Starting Tinyauth version: v5.2.0 stream=app
2026-10-23T21:24:52Z INF Starting server on http://0.0.0.0:3000 stream=app
2026-10-23T21:24:57Z WRN Client Error address=10.89.57.2:40894 client_ip=10.89.57.1 latency="132.492µs" method=GET path=/api/auth/traefik status=401 stream=http
2026-10-23T21:24:57Z INF Login successful stream=app username=admin
2026-10-23T21:24:57Z INF Request address=10.89.57.2:40910 client_ip=10.89.57.1 latency=44.491023ms method=POST path=/api/user/login status=200 stream=http
2026-10-23T21:24:57Z INF Request address=10.89.57.2:40894 client_ip=10.89.57.1 latency="34.62µs" method=GET path=/api/auth/traefik status=200 stream=http
# 应用层健康检查
docker exec tinyauth-v5.2.0-app tinyauth healthcheck

实测输出:

2026-10-23T21:23:16Z INF Performing health check app_url=http://127.0.0.1:3000 stream=app
2026-10-23T21:23:16Z INF Tinyauth is healthy response={"message":"Healthy","status":200} stream=app
# 查看当前生效配置(YAML 转储,排障首选)
docker exec tinyauth-v5.2.0-app tinyauth config

实测输出节选(ANSI 着色符已剥离):

Your current configuration in YAML is:

appUrl: http://auth.example.com
labelProvider: auto
database:
    driver: sqlite
    path: /data/tinyauth.db
server:
    port: 3000
    address: 0.0.0.0
auth:
    users:
        - admin:$2a$10$Edf9Rb8EMGOjyXIf1.yLCur3593A9DlmuZyi5tTeT0./1dmWv62UK
    subdomainsEnabled: true
    sessionExpiry: 86400
    loginTimeout: 300
    loginMaxRetries: 3
    trustedProxies:
        - 10.89.57.0/24
    acls:
        policy: allow
# 升级版本:改镜像标签 → 拉取 → 重建(数据卷保留,账号/配置不丢)
docker compose pull tinyauth
docker compose up -d tinyauth

实测输出(升级重启节选):

 Container tinyauth-v5.2.0-app Recreate
 Container tinyauth-v5.2.0-app Recreated
 Container tinyauth-v5.2.0-app Starting
 Container tinyauth-v5.2.0-app Started
tinyauth-v5.2.0-app Up 5 seconds (healthy)
# 停止 / 清理整套编排(external 网络与命名卷不受影响)
docker compose down

实测输出:

 Container tinyauth-v5.2.0-caddy  Stopped
 Container tinyauth-v5.2.0-caddy  Removed
 Container tinyauth-v5.2.0-nginx  Stopped
 Container tinyauth-v5.2.0-nginx  Removed
 Container tinyauth-v5.2.0-traefik  Stopped
 Container tinyauth-v5.2.0-traefik  Removed
 Container tinyauth-v5.2.0-whoami  Stopped
 Container tinyauth-v5.2.0-whoami  Removed
 Container tinyauth-v5.2.0-app  Stopped
 Container tinyauth-v5.2.0-app  Removed

💡 说明:docker compose down 不会删除 tinyauth-data 命名卷与 external 网络,重新 up -d 后数据原样恢复;确认要彻底清空数据时再显式执行 docker compose down -v。

十二、常见问题

Q1:按官方文档执行 docker compose exec tinyauth healthcheck 报错 exec: "healthcheck": executable file not found in $PATH?

实测报错原文:

OCI runtime exec failed: exec failed: unable to start container process: exec: "healthcheck": executable file not found in $PATH

定位:v5.2.0 官方镜像内二进制名为 tinyauth,healthcheck 是它的子命令而不是独立可执行文件。解决方案是把子命令完整写出来:

docker compose exec tinyauth tinyauth healthcheck

实测输出:

2026-10-23T21:23:16Z INF Performing health check app_url=http://127.0.0.1:3000 stream=app
2026-10-23T21:23:16Z INF Tinyauth is healthy response={"message":"Healthy","status":200} stream=app

Q2:直接用 curl 打 forward auth 端点(/api/auth/traefik 等)一直 400 Bad Request?

实测输出:

HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
Content-Length: 38

{"message":"Bad request","status":400}

这不是故障:三个端点依赖反代注入的请求头(如 Nginx 的 x-original-url、Traefik 的 x-forwarded-host)来识别受保护应用,脱离反代裸打必然 400。tinyauth 日志中对应的错误也印证了这一点:

2026-10-23T21:24:57Z ERR Failed to get proxy context from request error="failed to get context from any auth module" stream=app

正确验证方式是走反代访问受保护应用(见 10.4)。

Q3:能登录,但受保护应用始终跳回登录页或直接 500?

排查 Cookie 作用域。tinyauth 从 TINYAUTH_APPURL 推导 Cookie 域,域名不是三段式时 Cookie 会写在登录入口自身域上,受保护子域拿不到。实测对比:APPURL=http://auth.localtest(两段式)时 Cookie 被写到 Domain=auth.localtest:

Set-Cookie: tinyauth-session-1d49dbcf=fdb727c6-1f2e-428f-aa0e-54a7b49b9ffd; Path=/; Domain=auth.localtest; Expires=Sat, 24 Oct 2026 21:23:16 GMT; Max-Age=86399; HttpOnly; SameSite=Lax

同为 localtest 后缀的受保护域名访问时直接被拒(HTTP 500)。改用 http://auth.example.com(三段式)后 Cookie 正确落到 Domain=example.com,全链路打通。结论:APPURL 与所有受保护应用域名必须共享同一三段式主域。

Q4:重启后要重新登录?

是正常行为。会话数据不跨重启保留:重启后旧 Cookie 校验返回 401(见第五章实测),重新登录即可;用户账号、OIDC 密钥等数据由 /data 数据卷持久化,不受重启影响。

Q5:反代转发的客户端 IP 不对或跳转地址异常?

检查 TINYAUTH_AUTH_TRUSTEDPROXIES 是否覆盖反代来源网段。该值必须包含实际发起转发请求的一方(本文为 10.89.57.0/24),未声明时 tinyauth 不信任 X-Forwarded-For 头。可用 tinyauth config 转储确认生效值(见第七章实测)。

Q6:账号密码确认无误却始终登录失败?

检查 Compose 中 bcrypt 哈希的 $$ 转义。Compose 环境变量把 $ 写成 $$ 才能得到真实值;漏写(单 $)或错写(在 docker run 场景用了 $$)都会导致哈希比对失败。用 user verify 可离线核对账号串与密码是否匹配:

docker exec tinyauth-v5.2.0-app tinyauth user verify \
  --user 'admin:$2a$10$Edf9Rb8EMGOjyXIf1.yLCur3593A9DlmuZyi5tTeT0./1dmWv62UK' \
  --username admin --password q1w2e3r4

实测输出:

✓ User verified

密码与哈希不匹配时的报错原文:

Failed to execute command: command verify error: password is incorrect: crypto/bcrypt: hashedPassword is not the hash of the given password

Q7:想限制「登录用户」里谁能访问某个应用?

用应用级 ACL。示例:仅允许 nobody 访问 whoami(TINYAUTH_APPS_WHOAMI_CONFIG_DOMAIN=whoami.example.com、TINYAUTH_APPS_WHOAMI_USERS_ALLOW=nobody),已登录的 admin 访问时被拒:

HTTP/1.1 403 Forbidden
Content-Length: 36
Content-Type: application/json; charset=utf-8
X-Tinyauth-Location: http://auth.example.com/unauthorized?groupErr=false&ip=&resource=whoami&username=admin

{"message":"Forbidden","status":403}

把 allow 列表改回 admin 后同一请求返回 200。ACL 全量参数(users/ip/path/oauth/ldap 维度)见配置参数详解篇。

Q8:登出怎么做?

浏览器端在登录页点登出;API 层调 POST /api/user/logout,实测返回 200 并以 Max-Age=0 立即清除 Cookie:

HTTP/1.1 200 OK
Set-Cookie: tinyauth-session-26742baf=; Path=/; Domain=example.com; Expires=Fri, 23 Oct 2026 21:31:04 GMT; Max-Age=0; HttpOnly; SameSite=Lax

登出后再访问受保护应用回到 401 跳登录。

十三、参考链接