How To Install authentik on a Synology NAS Behind Cloudflare Tunnel
Deploy authentik on Synology DSM with Docker Compose, optionally expose it through Cloudflare Tunnel, and avoid stale-volume upgrade issues.
If you want self-hosted identity and SSO on a Synology NAS, authentik is a strong option. This guide walks through a clean Docker Compose deployment on Synology DSM, optional Cloudflare Tunnel exposure, and the one big issue that caused trouble during setup: stale Docker volumes from previous authentik versions.
This post is intentionally sanitized for public sharing. All IP addresses, domains, usernames, passwords, tokens, email addresses, and secret keys below are placeholders.
## What this setup does
- Runs authentik with Docker Compose on Synology DSM
- Uses PostgreSQL and Redis as required services
- Exposes authentik locally on port `9000`
- Optionally publishes the service through Cloudflare Tunnel
- Avoids a broken database state caused by reusing old Docker volumes across authentik version changes
## Requirements
- A Synology NAS with Docker or Container Manager available
- SSH access to the NAS
- A working folder for your compose project, for example:
- `/volume1/docker/authentik`
- Optional:
- A Cloudflare Tunnel already running on the NAS or another host
- A public hostname such as `auth.example.com`
## Recommended directory layout
Create a project folder such as:
```text
/volume1/docker/authentik
```
Inside it, keep at least:
- `compose.yml`
- `.env`
- `data/`
- `certs/`
- `custom-templates/`
## Docker Compose file
Below is a working compose file structure for Synology. Replace image tags and environment values as needed.
```yaml
services:
postgresql:
image: docker.io/library/postgres:16-alpine
container_name: authentik-postgresql
restart: unless-stopped
environment:
POSTGRES_DB: ${PG_DB}
POSTGRES_USER: ${PG_USER}
POSTGRES_PASSWORD: ${PG_PASS}
volumes:
- authentik_database:/var/lib/postgresql/data
redis:
image: docker.io/library/redis:alpine
container_name: authentik-redis
restart: unless-stopped
command: —save 60 1 —loglevel warning
volumes:
- authentik_redis:/data
server:
image: ${AUTHENTIK_IMAGE}:${AUTHENTIK_TAG}
container_name: authentik-server
restart: unless-stopped
command: server
depends_on:
- postgresql
- redis
environment:
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
AUTHENTIK_REDIS__HOST: redis
AUTHENTIK_POSTGRESQL__HOST: postgresql
AUTHENTIK_POSTGRESQL__NAME: ${PG_DB}
AUTHENTIK_POSTGRESQL__USER: ${PG_USER}
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
AUTHENTIK_ERROR_REPORTING__ENABLED: “false”
AUTHENTIK_EMAIL__FROM: ${AUTHENTIK_EMAIL__FROM}
AUTHENTIK_BOOTSTRAP_PASSWORD: ${AUTHENTIK_BOOTSTRAP_PASSWORD}
AUTHENTIK_BOOTSTRAP_TOKEN: ${AUTHENTIK_BOOTSTRAP_TOKEN}
ports:
- ”${COMPOSE_PORT_HTTP}:9000”
- ”${COMPOSE_PORT_HTTPS}:9443”
volumes:
- ./data:/media
- ./certs:/certs
- ./custom-templates:/templates
worker:
image: ${AUTHENTIK_IMAGE}:${AUTHENTIK_TAG}
container_name: authentik-worker
restart: unless-stopped
command: worker
depends_on:
- postgresql
- redis
environment:
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
AUTHENTIK_REDIS__HOST: redis
AUTHENTIK_POSTGRESQL__HOST: postgresql
AUTHENTIK_POSTGRESQL__NAME: ${PG_DB}
AUTHENTIK_POSTGRESQL__USER: ${PG_USER}
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
AUTHENTIK_ERROR_REPORTING__ENABLED: “false”
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./data:/media
- ./certs:/certs
- ./custom-templates:/templates
volumes:
authentik_database:
authentik_redis:
```
## Example `.env` file
Use strong random values for every secret.
```dotenv
AUTHENTIK_IMAGE=ghcr.io/goauthentik/server
AUTHENTIK_TAG=2025.10.3
PG_DB=authentik
PG_USER=authentik
PG_PASS=replace-with-a-long-random-password
AUTHENTIK_SECRET_KEY=replace-with-a-long-random-secret-key
AUTHENTIK_BOOTSTRAP_PASSWORD=replace-with-a-temporary-bootstrap-password
AUTHENTIK_BOOTSTRAP_TOKEN=replace-with-a-long-random-bootstrap-token
COMPOSE_PORT_HTTP=9000
COMPOSE_PORT_HTTPS=9443
AUTHENTIK_HOST=https://auth.example.com
AUTHENTIK_EMAIL__FROM=authentik@example.com
```
## Start authentik
From the project directory:
```bash
docker compose up -d
```
Then inspect startup logs:
```bash
docker logs —since 2m authentik-server
```
On a clean start, you want migrations to complete and the server to settle without repeated database errors.
## Local access
Test local access first before introducing Cloudflare:
```text
http://NAS-LAN-IP:9000/if/flow/default-authentication-flow/?next=%2F
```
Replace `NAS-LAN-IP` with your own private address.
Testing locally first makes it much easier to separate application problems from reverse proxy or tunnel problems.
## Optional: publish through Cloudflare Tunnel
If you already use `cloudflared`, add an ingress rule for authentik that points to the NAS over HTTP on port `9000`.
Example:
```yaml
ingress:
- hostname: auth.example.com
service: http://NAS-LAN-IP:9000
- service: http_status:404
```
Then restart the tunnel service or container.
At that point, both of these should work:
- Local: `http://NAS-LAN-IP:9000\`
- Public: `https://auth.example.com\`
## The problem we hit
The biggest issue during setup was not Cloudflare, the browser, or the admin password. The real problem was a broken PostgreSQL schema caused by stale Docker volumes from earlier authentik attempts and version switching.
The key error in the logs looked like this:
```text
column authentik_core_group.parent_id does not exist
```
There were also related errors such as:
```text
Internal Server Error: /api/v3/outposts/instances/
found incompatible flow plan, invalidating run
```
Those symptoms caused logins to fail in confusing ways even after:
- resetting the admin password
- generating a recovery key
- verifying the admin user was active
In short: the app looked alive, but the database state was wrong.
## Why this happened
On Synology, deleting and recreating a Docker Compose project does not always remove named Docker volumes. That means a fresh-looking deployment may still be attached to an old PostgreSQL data directory.
If you deploy one authentik version, then switch versions, then reuse the same database volume, you can end up with schema drift or incompatible state.
## How we fixed it
The permanent fix was to remove the stale Docker volumes completely and start from a truly clean database.
First, inspect volumes:
```bash
docker volume ls | grep authentik
```
Stop the stack:
```bash
cd /volume1/docker/authentik
docker compose down
```
Then remove the old authentik-related volumes. Your names may differ, but in this case they included entries like:
```text
authentik_authentik_database
authentik_authentik_pgdata
authentik_authentik_redis
authentik_database
```
Remove the stale ones:
```bash
docker volume rm VOLUME_NAME
```
Repeat until:
```bash
docker volume ls | grep authentik
```
returns nothing relevant from the old deployment.
If you also store bind-mounted application files locally, clear those too if you want a full reset:
```bash
rm -rf /volume1/docker/authentik/data/*
rm -rf /volume1/docker/authentik/certs/*
rm -rf /volume1/docker/authentik/custom-templates/*
```
After that, start the stack again:
```bash
docker compose up -d
docker logs —since 2m authentik-server
```
Once the old volumes were gone, authentik booted correctly and login started working.
## Important lessons learned
- Test authentik locally before troubleshooting Cloudflare Tunnel
- Do not assume deleting a Synology project also deleted Docker volumes
- Do not switch authentik versions back and forth on the same database unless the upgrade path is known-good
- If authentik shows repeated login flow errors, inspect the server logs before resetting passwords repeatedly
- If PostgreSQL schema errors appear, fix the database state first
## Safe troubleshooting checklist
If authentik is up but login is failing:
-
Check the local URL first
-
Review `authentik-server` logs
-
Look for database schema errors
-
Confirm PostgreSQL and Redis are healthy
-
Only reset passwords after the database is known-good
-
If you reused volumes across versions, strongly consider a clean rebuild
## Security notes before publishing your own guide
Before posting screenshots, configs, or logs publicly, remove or replace:
- Private IP addresses
- Public domains
- Email addresses
- Usernames
- Bootstrap tokens
- Session or recovery links
- Secret keys
- Database passwords
- Any Cloudflare Tunnel identifiers
Treat bootstrap tokens, recovery links, and temporary passwords as compromised if they were ever pasted into chat, screenshots, or public notes. Rotate them after the system is stable.
## Final result
After removing the stale volumes and redeploying cleanly, authentik worked correctly on the Synology NAS both locally and through Cloudflare Tunnel.
If you are doing a similar deployment and authentik behaves strangely even though the containers are running, check the database volumes early. That ended up being the difference between a half-working install and a fully working one.