Understand the repository structure, configuration files, and data organization for the Headscale + Tailscale Docker Stack.
Repository structure
headscale-tailscale-docker/
├── config/ # Headscale configuration
│ ├── config.yaml # Main Headscale configuration
│ └── policy.json # ACL policies (optional)
│
├── data/ # Headscale persistent data
│ ├── db.sqlite # SQLite database (if using SQLite)
│ ├── private.key # Server private key
│ └── noise_private.key # Noise protocol key
│
├── headplane/ # Headplane web GUI configuration
│ └── config.yaml # Headplane settings
│
├── logs/ # Application logs
│ └── nginx/ # nginx access and error logs
│ ├── access.log
│ └── error.log
│
├── certbot/ # SSL/TLS certificates
│ ├── conf/ # Let's Encrypt certificates
│ └── www/ # ACME challenge files
│
├── scripts/ # Helper scripts
│ ├── headscale.sh # Headscale management wrapper
│ ├── nginx.sh # nginx management script
│ ├── backup.sh # Backup automation
│ ├── setup.sh # Initial setup wizard
│ └── lefthook/ # Git hooks for security
│
├── tailscale-configs/ # Client configuration templates
│ ├── linux/ # Linux systemd units
│ ├── macos/ # macOS LaunchDaemons
│ ├── windows/ # Windows PowerShell scripts
│ ├── docker/ # Docker sidecar examples
│ └── README.md # Platform-specific guides
│
├── docs/ # Documentation
│ ├── QUICKSTART.md
│ ├── QUICK_START_GUI.md
│ ├── BEST_PRACTICES.md
│ ├── DEPLOYMENT.md
│ ├── NETWORKING.md
│ ├── SECURITY.md
│ ├── GUI_SETUP.md
│ └── NGINX_CONFIGURATION.md
│
├── docker-compose.yml # Production service definitions
├── docker-compose.override.example.yml # Development overrides
├── nginx.conf # Production nginx (SSL/TLS)
├── nginx.dev.conf # Development nginx (HTTP)
├── .env.example # Environment variable template
├── .gitignore # Git exclusions
├── lefthook.yml # Git hooks configuration
├── Caddyfile # Alternative Caddy config
├── CLAUDE.md # AI assistant guidance
└── README.md # Main documentation
Configuration files
Headscale configuration
Location : config/config.yaml
Purpose : Main Headscale server configuration including:
Server URL and listening addresses
Database connection (PostgreSQL or SQLite)
DERP server settings
DNS and MagicDNS configuration
IP address prefixes (IPv4/IPv6)
Logging and policy settings
Critical parameters :
server_url : http://localhost:8000 # Must match deployment
listen_addr : 0.0.0.0:8080
metrics_listen_addr : 0.0.0.0:9090
database :
type : postgres
postgres :
host : postgres
pass : changeme # Must match .env POSTGRES_PASSWORD
The database password must be synchronized between config.yaml and .env or the stack will fail to start.
ACL policies
Location : config/policy.json
Purpose : Access control rules defining:
User groups
Tag ownership
Traffic rules between nodes
Auto-approval for routes and exit nodes
SSH access policies
Example :
{
"tagOwners" : {
"tag:server" : [ "user:admin" ],
"tag:client" : [ "user:alice" , "user:bob" ]
},
"acls" : [
{
"action" : "accept" ,
"src" : [ "tag:client" ],
"dst" : [ "tag:server:*" ]
}
]
}
See ACL policies documentation for complete syntax.
nginx configuration
Production : nginx.conf
SSL/TLS termination with Let’s Encrypt
HTTP to HTTPS redirection
Rate limiting (3-tier strategy)
Security headers (HSTS, CSP, X-Frame-Options)
Ports 80 and 443
Development : nginx.dev.conf
HTTP only (no SSL)
No rate limiting
Port 8000
Simplified for local testing
Switching : Use docker-compose.override.yml to select dev config.
Docker Compose
Production : docker-compose.yml
Defines 5 services:
headscale - Control plane server
nginx - Reverse proxy
headplane - Web GUI
postgres - Database
certbot - SSL certificate management
Development override : docker-compose.override.example.yml
Modifies production config for local dev:
Changes nginx config to nginx.dev.conf
Switches port from 80/443 to 8000
Simplifies for HTTP-only testing
Environment variables
Location : .env (create from .env.example)
Variables :
# Domain for production deployment
HEADSCALE_DOMAIN = headscale.example.com
# PostgreSQL credentials
POSTGRES_DB = headscale
POSTGRES_USER = headscale
POSTGRES_PASSWORD = changeme # CHANGE THIS
# Timezone
TZ = UTC
# Headplane authentication
HEADPLANE_API_KEY = your-api-key-here
HEADPLANE_COOKIE_SECRET = exactly-32-characters-required!
HEADPLANE_COOKIE_SECRET must be exactly 32 characters or Headplane will fail to start.
Data directories
Headscale data
Location : data/
Contents :
db.sqlite - Local SQLite database (if not using PostgreSQL)
private.key - Server’s WireGuard private key
noise_private.key - Noise protocol encryption key
Backup importance : CRITICAL
These keys are essential for:
Decrypting existing connections
Maintaining node registrations
Preserving network state
Backup command :
tar -czf headscale-data-backup- $( date +%Y%m%d ) .tar.gz data/
PostgreSQL data
Location : Docker volume postgres-data
Contents :
User accounts
Node registrations
Pre-auth keys
Route advertisements
Audit logs
Backup :
# Dump database
docker exec headscale-db pg_dump -U headscale headscale > backup.sql
# Restore
cat backup.sql | docker exec -i headscale-db psql -U headscale
See Backup & Restore for complete procedures.
SSL certificates
Location : certbot/conf/
Contents :
live/<domain>/fullchain.pem - Certificate + intermediate chain
live/<domain>/privkey.pem - Private key
archive/<domain>/ - All certificate versions
renewal/<domain>.conf - Renewal configuration
Auto-renewal : Certbot container checks every 12 hours.
Manual renewal :
docker compose exec certbot certbot renew
docker compose restart nginx
Logs
nginx logs : logs/nginx/
access.log - All HTTP requests with timing
error.log - nginx errors and warnings
Container logs : Via Docker
# View all logs
docker compose logs -f
# Specific service
docker compose logs -f headscale
# Last 100 lines
docker compose logs --tail=100 headscale
Helper scripts
headscale.sh
Location : scripts/headscale.sh
Purpose : Wrapper for common Headscale operations
Usage :
./scripts/headscale.sh users create alice
./scripts/headscale.sh keys create alice --reusable --expiration 24h
./scripts/headscale.sh nodes list
./scripts/headscale.sh routes enable --route-id 1
See headscale.sh reference for all commands.
nginx.sh
Location : scripts/nginx.sh
Purpose : nginx management and diagnostics
Usage :
./scripts/nginx.sh status
./scripts/nginx.sh logs
./scripts/nginx.sh ssl-info
./scripts/nginx.sh test
See nginx.sh reference for complete documentation.
backup.sh
Location : scripts/backup.sh
Purpose : Automated backup of PostgreSQL, config, and data
Usage :
# Manual backup
./scripts/backup.sh
# Automated (cron)
0 2 * * * /path/to/scripts/backup.sh
See backup.sh reference for details.
Client configuration templates
Location : tailscale-configs/
Platform-specific configuration files and setup scripts:
Linux
Files : tailscale-configs/linux/
tailscale-headscale.service - systemd unit
setup.sh - Automated installation script
Usage :
cd tailscale-configs/linux
sudo ./setup.sh
macOS
Files : tailscale-configs/macos/
com.tailscale.headscale.plist - LaunchDaemon
install.sh - Installation script
Windows
Files : tailscale-configs/windows/
connect.ps1 - PowerShell connection script
setup.ps1 - Automated setup
Docker
Files : tailscale-configs/docker/
docker-compose.example.yml - Sidecar pattern example
Dockerfile.tailscale - Custom image with Tailscale
Git configuration
.gitignore
Excludes sensitive and generated files:
.env
data/
logs/
certbot/
docker-compose.override.yml
postgres-data/
lefthook.yml
Purpose : Git hooks for security
Prevents committing:
.env files with credentials
.DS_Store macOS artifacts
Database files
Private keys
Setup :
brew install lefthook
lefthook install
See Lefthook documentation for details.
File permissions
Important permissions
Private keys (data/private.key, data/noise_private.key):
Configuration files :
chmod 644 config/config.yaml
chmod 644 config/policy.json
Scripts :
SSL certificates :
chmod 600 certbot/conf/live/ * /privkey.pem
chmod 644 certbot/conf/live/ * /fullchain.pem
Never commit private keys or .env files to version control. Use .gitignore to prevent accidental exposure.
Development vs production
Development files
Active :
nginx.dev.conf (HTTP only)
docker-compose.override.yml (port 8000)
.env with localhost settings
server_url : http://localhost:8000
Production files
Active :
nginx.conf (SSL/TLS)
docker-compose.yml (ports 80, 443)
.env with public domain
certbot/ with Let’s Encrypt certificates
server_url : https://yourdomain.com
Maintenance
Regular tasks
Daily :
Review logs/nginx/access.log for unusual activity
Check docker compose ps for service health
Weekly :
Run backup script
Check disk usage: du -sh data/ certbot/ logs/
Review expired pre-auth keys
Monthly :
Update Docker images: docker compose pull && docker compose up -d
Clean old logs: find logs/ -mtime +30 -delete
Test backup restoration
Cleanup
Remove old backups :
find . -name "headscale-backup-*.tar.gz" -mtime +30 -delete
Clean Docker :
Expire offline nodes :
docker exec headscale headscale nodes expire --all-offline
Architecture System architecture and components
Configuration Detailed configuration reference
Backup & Restore Data protection procedures
Security Security best practices