Deploy Tinyauth on a VPS
Lightweight authentication middleware (~20 MB) with local logins, OAuth, TOTP 2FA, LDAP, per-app ACLs, and OIDC server — protects any app behind Traefik, Nginx, or Caddy.
At a Glance
| Project | Tinyauth v5 |
| License | MIT |
| Recommended Plan | RamNode Cloud VPS 1 GB+ (negligible resource usage) |
| OS | Ubuntu 22.04 / 24.04 LTS |
| Stack | Docker, Traefik v3, Let's Encrypt |
| Estimated Setup Time | 15–20 minutes |
Prerequisites
- A RamNode VPS with at least 1 GB RAM
- Ubuntu 22.04 or 24.04 LTS with Docker and Docker Compose installed
- A registered domain name with DNS managed through your provider
- SSH access with a non-root sudo user
Configure DNS Records
Tinyauth uses cookie-based auth scoped to the parent domain. All protected apps must share the same root domain.
| Hostname | Type | Value |
|---|---|---|
tinyauth.example.com | A | YOUR_VPS_IP |
whoami.example.com | A | YOUR_VPS_IP |
Create a Tinyauth User
docker run -i -t --rm ghcr.io/steveiliop56/tinyauth:v5 user create --interactiveSelect "format for docker" so bcrypt $ characters are escaped with $ for Docker Compose. Copy the output.
Docker Compose Setup
mkdir -p ~/tinyauth && cd ~/tinyauthservices:
traefik:
image: traefik:v3.3
container_name: traefik
restart: unless-stopped
command:
- --api.dashboard=true
- --providers.docker=true
- --providers.docker.exposedbydefault=false
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
- --certificatesresolvers.letsencrypt.acme.httpchallenge=true
- --certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web
- --certificatesresolvers.letsencrypt.acme.email=you@example.com
- --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json
- --entrypoints.web.http.redirections.entrypoint.to=websecure
- --entrypoints.web.http.redirections.entrypoint.scheme=https
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- letsencrypt:/letsencrypt
tinyauth:
image: ghcr.io/steveiliop56/tinyauth:v5
container_name: tinyauth
restart: unless-stopped
environment:
- TINYAUTH_APPURL=https://tinyauth.example.com
- TINYAUTH_AUTH_USERS=admin:$2a$10$YOUR_BCRYPT_HASH
- TINYAUTH_AUTH_SECURECOOKIE=true
- TINYAUTH_AUTH_SESSIONEXPIRY=86400
- TINYAUTH_AUTH_LOGINMAXRETRIES=5
- TINYAUTH_AUTH_LOGINTIMEOUT=300
labels:
traefik.enable: true
traefik.http.routers.tinyauth.rule: Host(`tinyauth.example.com`)
traefik.http.routers.tinyauth.entrypoints: websecure
traefik.http.routers.tinyauth.tls.certresolver: letsencrypt
traefik.http.middlewares.tinyauth.forwardauth.address: http://tinyauth:3000/api/auth/traefik
whoami:
image: traefik/whoami:latest
container_name: whoami
restart: unless-stopped
labels:
traefik.enable: true
traefik.http.routers.whoami.rule: Host(`whoami.example.com`)
traefik.http.routers.whoami.entrypoints: websecure
traefik.http.routers.whoami.tls.certresolver: letsencrypt
traefik.http.routers.whoami.middlewares: tinyauth
volumes:
letsencrypt:Launch the Stack
docker compose up -d
docker compose psAll three containers should show Up. Visit https://whoami.example.com — you should be redirected to the Tinyauth login page.
Add GitHub OAuth (Optional)
Create a GitHub OAuth App with callback URL: https://tinyauth.example.com/api/oauth/callback/github
# Add to tinyauth service environment:
- TINYAUTH_OAUTH_PROVIDERS_GITHUB_CLIENTID=your_client_id
- TINYAUTH_OAUTH_PROVIDERS_GITHUB_CLIENTSECRET=your_client_secret
- TINYAUTH_OAUTH_WHITELIST=yourgithub@email.comdocker compose up -dEnable Two-Factor Authentication (Optional)
docker run -i -t --rm ghcr.io/steveiliop56/tinyauth:v5 user create --interactive --totpThe CLI outputs a user string with a TOTP secret and a QR code for your authenticator app. Replace the TINYAUTH_AUTH_USERS value and restart.
Configure Per-App Access Controls
# Restrict whoami to admin only:
- TINYAUTH_APPS_WHOAMI_CONFIG_DOMAIN=whoami.example.com
- TINYAUTH_APPS_WHOAMI_USERS_ALLOW=adminAdditional ACL options include:
users.allow/users.block— by usernameoauth.whitelist/oauth.groups— OAuth-basedip.allow/ip.block/ip.bypass— IP filteringpath.allow/path.block— URL path restrictions
Protect Additional Applications
my-app:
image: my-app-image:latest
restart: unless-stopped
labels:
traefik.enable: true
traefik.http.routers.my-app.rule: Host(`myapp.example.com`)
traefik.http.routers.my-app.entrypoints: websecure
traefik.http.routers.my-app.tls.certresolver: letsencrypt
traefik.http.routers.my-app.middlewares: tinyauthThe key line is middlewares: tinyauth — this routes auth through Tinyauth before reaching the app.
Production Hardening
socket-proxy:
image: tecnativa/docker-socket-proxy
container_name: socket-proxy
restart: unless-stopped
environment:
CONTAINERS: 1
SERVICES: 0
TASKS: 0
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- proxycat > .env << 'EOF'
TINYAUTH_USERS=admin:$2a$10$YOUR_BCRYPT_HASH
GITHUB_CLIENT_ID=your_client_id
GITHUB_CLIENT_SECRET=your_client_secret
OAUTH_WHITELIST=yourgithub@email.com
EOF
chmod 600 .envsudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enablewatchtower:
image: containrrr/watchtower
container_name: watchtower
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- WATCHTOWER_CLEANUP=true
- WATCHTOWER_SCHEDULE=0 0 4 * * *Troubleshooting
- Login page not appearing: Verify the forwardauth middleware label matches in both the Tinyauth service and protected app's router
- Cookie not working: Confirm all apps share the same root domain and
SECURECOOKIE=trueis set with HTTPS - OAuth redirect fails: Ensure GitHub callback URL matches exactly
- Container won't start: Tinyauth v5 validates all env vars on startup — check
docker compose logs tinyauth
