- Add Groups field to User struct with JSON storage - Include GetGroups() and SetGroups() helper methods - Extract groups from OIDC claims in FromClaim() - Add database migration 202509161200 for groups column - Update config-example.yaml with groups scope - Add comprehensive documentation and testing
Headscale Docker Development Environment
This directory contains a complete Docker Compose setup for running Headscale with Tailscale clients in a local development environment.
Overview
This setup includes:
- Headscale server: The control plane server
- Two Tailscale clients: Simulated nodes that connect through Headscale
- Test web server: Optional nginx server for connectivity testing
- Helper scripts: Automated setup and management tools
Architecture
┌─────────────────────────────────────────┐
│ Docker Network (10.99.0.0/24) │
├─────────────────────────────────────────┤
│ │
│ ┌──────────────┐ │
│ │ Headscale │ 10.99.0.10 │
│ │ Server │ :8080 (API) │
│ │ │ :9090 (Metrics) │
│ │ │ :50443 (gRPC) │
│ └──────┬───────┘ │
│ │ │
│ ┌────┴────┬─────────┐ │
│ │ │ │ │
│ ┌──▼───┐ ┌──▼───┐ ┌──▼───┐ │
│ │Client│ │Client│ │ Web │ │
│ │ 1 │ │ 2 │ │Server│ │
│ │.0.21 │ │.0.22 │ │.0.30 │ │
│ └──────┘ └──────┘ └──────┘ │
│ │
│ Tailscale Network (100.64.0.0/16) │
└─────────────────────────────────────────┘
Quick Start
1. Start the environment
# Start all services and automatically set up users/keys
make up
# Or manually:
docker compose up -d
make setup
2. Check status
# View all nodes
make status
# Watch logs
make logs
3. Test connectivity
# Test ping between clients
make ping-test
# Test web server access
make test-web
✅ Verified Working Setup
This environment has been tested and verified working with:
- Headscale:
headscale/headscale:latest(as of September 2024) - Tailscale:
tailscale/tailscale:latest - Network: Docker bridge network
10.99.0.0/24 - Tailscale Network:
100.64.0.0/10with IPv6fd7a:115c:a1e0::/48 - Port Mapping: Host port
8180→ Container port8080(Headscale API)
Test Results
- ✅ Headscale server starts and serves on port 8180
- ✅ Both Tailscale clients register automatically with pre-auth keys
- ✅ Clients receive IP addresses:
100.64.0.1and100.64.0.2 - ✅ Bidirectional ping works between clients
- ✅
tailscale statusshows both nodes online - ✅ Traffic flows through encrypted Tailscale tunnel
Key Insights from Testing
Network Architecture: This setup demonstrates two distinct networking layers:
- Docker Bridge Network (
10.99.0.0/24) - Physical layer for container communication - Tailscale Overlay Network (
100.64.0.0/10) - Encrypted VPN tunnel for secure communication
When clients ping each other, the traffic uses Tailscale IPs (100.64.x.x) but actually travels through the Docker network infrastructure, demonstrating how Tailscale creates an encrypted overlay on top of existing network infrastructure.
Available Commands
Run make help to see all available commands:
make up- Start all services with automatic setupmake down- Stop all servicesmake clean- Remove everything including volumesmake status- Show status of all nodesmake logs- Show logs from all servicesmake ping-test- Test connectivity between clientsmake shell-headscale- Open shell in Headscale containermake shell-client1- Open shell in Client1 containermake shell-client2- Open shell in Client2 container
Manual Operations
Creating users
docker exec headscale-server headscale users create myuser
Generating pre-auth keys
docker exec headscale-server headscale preauthkeys create \
--user myuser \
--reusable \
--expiration 24h
Listing nodes
docker exec headscale-server headscale nodes list
Manual node registration
If automatic registration fails:
- Start the client registration:
docker exec tailscale-client1 tailscale up \
--login-server=http://headscale:8080
-
Copy the node key from the output
-
Register the node:
docker exec headscale-server headscale nodes register \
--user testuser \
--key <nodekey>
Configuration
Headscale Configuration
Edit headscale-config.yaml to modify:
- IP ranges for nodes
- DNS settings
- DERP server configuration
- Logging levels
ACL Policy
Edit acl.hujson to modify access control rules. Default policy allows all traffic between all nodes.
Environment Variables
The .env file contains:
COMPOSE_PROJECT_NAME: Docker Compose project nameTS_AUTHKEY_CLIENT1: Pre-auth key for client 1TS_AUTHKEY_CLIENT2: Pre-auth key for client 2
Testing Connectivity
Between Tailscale clients
# From client1 to client2
docker exec tailscale-client1 tailscale ping client2
# Using regular ping with Tailscale IPs
docker exec tailscale-client1 ping -c 3 100.64.0.2
Through the web server
# Access the test web server
docker exec tailscale-client1 curl http://webserver
Troubleshooting
Clients not connecting
- Check Headscale logs:
make logs-headscale
- Check client logs:
make logs-clients
- Verify pre-auth keys are set:
cat .env
- Try manual registration:
make register-manual
Network issues
- Verify Docker network:
docker network inspect headscale-dev_headscale-net
- Check Tailscale status in clients:
docker exec tailscale-client1 tailscale status
docker exec tailscale-client2 tailscale status
- Test basic connectivity:
docker exec tailscale-client1 ping headscale
Reset everything
make clean
make up
Development Workflow
Using local Headscale build
- Build Headscale from source:
make build-local
- Update
docker-compose.yml:
headscale:
image: headscale:local # Instead of headscale/headscale:latest
- Restart:
make down
make up
Modifying ACL policies
- Edit
acl.hujson - Restart Headscale to apply changes:
docker compose restart headscale
Adding more clients
- Copy the client service definition in
docker-compose.yml - Update the container name, hostname, and IP address
- Add a new auth key environment variable
- Run
make setupto generate a new key - Start the new client
Security Notes
- This setup is for development only
- Uses HTTP instead of HTTPS for simplicity
- Pre-auth keys have 24-hour expiration by default
- All traffic between nodes is allowed by default ACL
Clean Up
To completely remove the environment:
make clean
This removes:
- All containers
- All volumes (including Headscale database)
- Generated auth keys in
.env