docs: comprehensive documentation overhaul
Add complete documentation structure: - Frontend architecture documentation - Database schema documentation - Deployment guides (Docker, Traefik, Authentik, Environment) - Development guides (Setup, Testing, Contributing, Quality Gates) - Deployment architecture documentation - Updated docs README with complete navigation All new features and APIs are now documented. Quality gates: docs only, no code changes
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
# Authentik OAuth Configuration
|
||||
|
||||
## Overview
|
||||
|
||||
Headquarter uses Authentik as its OAuth2 provider for authentication. This guide covers setting up Authentik and configuring Headquarter to work with it.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Running Authentik instance
|
||||
- Admin access to Authentik
|
||||
- Headquarter deployed and accessible
|
||||
|
||||
## Authentik Setup
|
||||
|
||||
### Step 1: Create Application
|
||||
|
||||
1. Log in to Authentik Admin interface
|
||||
2. Navigate to **Applications** → **Applications**
|
||||
3. Click **Create**
|
||||
4. Fill in:
|
||||
- **Name**: Headquarter
|
||||
- **Slug**: `headquarter-web` (or your preferred slug)
|
||||
- **Provider**: Create new
|
||||
|
||||
### Step 2: Create OAuth Provider
|
||||
|
||||
1. In the provider creation form:
|
||||
- **Name**: Headquarter OAuth
|
||||
- **Authentication flow**: `default-authentication-flow`
|
||||
- **Authorization flow**: `default-provider-authorization-explicit-consent`
|
||||
- **Client type**: Confidential
|
||||
- **Client ID**: Generate or use your own UUID
|
||||
- **Client Secret**: Generate strong secret
|
||||
- **Redirect URIs**: `https://api.yourdomain.com/auth/callback`
|
||||
|
||||
2. **Advanced protocol settings**:
|
||||
- **Signing Key**: Select a signing key (required)
|
||||
- **Access Token validity**: Minutes (default: 5)
|
||||
- **Refresh Token validity**: Days (default: 30)
|
||||
|
||||
3. Save provider
|
||||
|
||||
### Step 3: Configure Application
|
||||
|
||||
1. Return to Application configuration
|
||||
2. Select the created provider
|
||||
3. Save application
|
||||
|
||||
### Step 4: Verify URLs
|
||||
|
||||
Note these URLs from your Authentik instance:
|
||||
- **Authorize URL**: `https://auth.yourdomain.com/application/o/authorize/`
|
||||
- **Token URL**: `https://auth.yourdomain.com/application/o/token/`
|
||||
- **UserInfo URL**: `https://auth.yourdomain.com/application/o/userinfo/`
|
||||
- **JWKS URL**: `https://auth.yourdomain.com/application/o/headquarter-web/jwks/`
|
||||
|
||||
## Headquarter Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Add to your `.env` file:
|
||||
|
||||
```bash
|
||||
# Authentik Configuration
|
||||
AUTHENTIK_DOMAIN=auth.yourdomain.com
|
||||
AUTHENTIK_CLIENT_ID=your-client-id-uuid
|
||||
AUTHENTIK_CLIENT_SECRET=your-generated-secret
|
||||
AUTHENTIK_APPLICATION_SLUG=headquarter-web
|
||||
AUTHENTIK_AUDIENCE=your-client-id-uuid
|
||||
|
||||
# Optional: Override default URLs if needed
|
||||
# AUTHENTIK_AUTHORIZE_URL=https://auth.yourdomain.com/application/o/authorize/
|
||||
# AUTHENTIK_TOKEN_URL=https://auth.yourdomain.com/application/o/token/
|
||||
# AUTHENTIK_JWKS_URL=https://auth.yourdomain.com/application/o/headquarter-web/jwks/
|
||||
# AUTHENTIK_ISSUER=https://auth.yourdomain.com/application/o/headquarter-web/
|
||||
```
|
||||
|
||||
### Important Notes
|
||||
|
||||
- **AUTHENTIK_CLIENT_ID**: The UUID from Authentik (used for OAuth)
|
||||
- **AUTHENTIK_APPLICATION_SLUG**: The URL-friendly name (e.g., `headquarter-web`)
|
||||
- **AUTHENTIK_AUDIENCE**: Usually same as Client ID
|
||||
|
||||
## User Synchronization
|
||||
|
||||
On first login, Headquarter creates a local user record:
|
||||
|
||||
```python
|
||||
user = User(
|
||||
email="user@example.com",
|
||||
name="User Name",
|
||||
authentik_id="authentik-user-id",
|
||||
)
|
||||
```
|
||||
|
||||
### Synced Fields
|
||||
|
||||
| Authentik Field | Headquarter Field |
|
||||
|----------------|-------------------|
|
||||
| email | email |
|
||||
| name | name |
|
||||
| sub (user ID) | authentik_id |
|
||||
| groups | (future: team membership) |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Redirect URI Error
|
||||
|
||||
**Problem**: "Redirect URI Error" from Authentik
|
||||
|
||||
**Solution**:
|
||||
1. Check redirect URI in Authentik matches exactly
|
||||
2. Must include protocol: `https://api.yourdomain.com/auth/callback`
|
||||
3. No trailing slash difference
|
||||
|
||||
### Invalid Client
|
||||
|
||||
**Problem**: "invalid_client" error
|
||||
|
||||
**Solution**:
|
||||
1. Verify Client ID matches
|
||||
2. Verify Client Secret is correct
|
||||
3. Check application slug in URLs
|
||||
|
||||
### Missing Refresh Token
|
||||
|
||||
**Problem**: Authentik doesn't return refresh token
|
||||
|
||||
**Solution**:
|
||||
This is normal. Headquarter creates its own session cookies and doesn't need refresh tokens from Authentik.
|
||||
|
||||
### CORS Errors
|
||||
|
||||
**Problem**: CORS errors in browser
|
||||
|
||||
**Solution**:
|
||||
1. Ensure API domain is in CORS origins
|
||||
2. Check `WEB_BASE_URL` environment variable
|
||||
3. Verify cookies have correct domain
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
1. **Use HTTPS** - Never use HTTP in production
|
||||
2. **Strong Client Secret** - Use generated secret, don't reuse
|
||||
3. **Short Token Lifetime** - Keep access tokens short-lived
|
||||
4. **Validate State** - Always verify state parameter
|
||||
5. **Secure Cookies** - Use httpOnly, Secure, SameSite
|
||||
|
||||
## Advanced Configuration
|
||||
|
||||
### Custom Claims
|
||||
|
||||
To add custom claims to the token:
|
||||
|
||||
1. In Authentik, go to **Customization** → **Property Mappings**
|
||||
2. Create new **Scope Mapping**
|
||||
3. Add custom attributes
|
||||
4. Assign to provider
|
||||
|
||||
### Group Mapping
|
||||
|
||||
For team/organization support:
|
||||
|
||||
1. Configure group property mapping in Authentik
|
||||
2. Headquarter will sync groups on login
|
||||
3. Use groups for authorization
|
||||
|
||||
### Multiple Applications
|
||||
|
||||
If running multiple environments:
|
||||
|
||||
1. Create separate applications in Authentik
|
||||
2. Use different client IDs
|
||||
3. Configure environment-specific redirect URIs
|
||||
|
||||
## Testing
|
||||
|
||||
### Manual Test
|
||||
|
||||
1. Visit `https://app.yourdomain.com`
|
||||
2. Click "Login"
|
||||
3. Should redirect to Authentik
|
||||
4. Login with Authentik credentials
|
||||
5. Should redirect back to app, logged in
|
||||
|
||||
### API Test
|
||||
|
||||
```bash
|
||||
# Check auth endpoint
|
||||
curl https://api.yourdomain.com/auth/me
|
||||
# Should return 401 (not authenticated)
|
||||
|
||||
# After login, should return user data
|
||||
curl https://api.yourdomain.com/auth/me --cookie "session=..."
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- [Authentik Documentation](https://goauthentik.io/docs/)
|
||||
- [OAuth2 Provider Setup](https://goauthentik.io/docs/providers/oauth2/)
|
||||
- [Headquarter Auth Documentation](../features/auth.md)
|
||||
@@ -0,0 +1,266 @@
|
||||
# Docker Deployment Guide
|
||||
|
||||
## Overview
|
||||
|
||||
Headquarter is deployed as a multi-container Docker application using Docker Compose. The stack includes:
|
||||
|
||||
- **API** (FastAPI/Python)
|
||||
- **Web** (React/Vite)
|
||||
- **PostgreSQL** (Database)
|
||||
- **Redis** (Cache)
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Docker Engine 20.10+
|
||||
- Docker Compose 2.0+
|
||||
- 2GB RAM minimum
|
||||
- 10GB disk space
|
||||
|
||||
### Development Deployment
|
||||
|
||||
```bash
|
||||
# Clone repository
|
||||
git clone https://github.com/your-org/headquarter.git
|
||||
cd headquarter
|
||||
|
||||
# Copy environment file
|
||||
cp .env.example .env
|
||||
# Edit .env with your settings
|
||||
|
||||
# Start all services
|
||||
docker compose up -d
|
||||
|
||||
# Run database migrations
|
||||
docker compose exec api alembic upgrade head
|
||||
|
||||
# Access the app
|
||||
# Frontend: http://localhost:5173
|
||||
# API: http://localhost:8000
|
||||
```
|
||||
|
||||
### Production Deployment
|
||||
|
||||
```bash
|
||||
# Use production compose file
|
||||
cp .env.example .env
|
||||
# Configure production values in .env
|
||||
|
||||
docker compose -f docker-compose.traefik.yml up -d
|
||||
|
||||
# Run migrations
|
||||
docker compose -f docker-compose.traefik.yml exec api alembic upgrade head
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Required variables:
|
||||
|
||||
```bash
|
||||
# Domains
|
||||
API_DOMAIN=api.yourdomain.com
|
||||
WEB_DOMAIN=app.yourdomain.com
|
||||
|
||||
# Database
|
||||
POSTGRES_USER=headquarter
|
||||
POSTGRES_PASSWORD=secure-password
|
||||
POSTGRES_DB=headquarter
|
||||
|
||||
# Authentik OAuth
|
||||
AUTHENTIK_DOMAIN=auth.yourdomain.com
|
||||
AUTHENTIK_CLIENT_ID=your-client-id
|
||||
AUTHENTIK_CLIENT_SECRET=your-client-secret
|
||||
AUTHENTIK_APPLICATION_SLUG=headquarter
|
||||
|
||||
# Session
|
||||
SESSION_SECRET=your-session-secret-min-32-chars
|
||||
|
||||
# Storage
|
||||
REPO_BASE_PATH=/data/repos
|
||||
```
|
||||
|
||||
### Volume Mounts
|
||||
|
||||
| Volume | Container Path | Purpose |
|
||||
|--------|---------------|---------|
|
||||
| postgres_data | /var/lib/postgresql/data | Database persistence |
|
||||
| repo_data | /data/repos | Git repositories |
|
||||
| avatar_uploads | /app/uploads | User avatars |
|
||||
|
||||
## Docker Compose Files
|
||||
|
||||
### docker-compose.yml (Development)
|
||||
|
||||
Standard development setup with:
|
||||
- Hot reload for API
|
||||
- Vite dev server for frontend
|
||||
- Direct port access
|
||||
- Local PostgreSQL
|
||||
|
||||
### docker-compose.traefik.yml (Production)
|
||||
|
||||
Production setup with:
|
||||
- Traefik reverse proxy
|
||||
- Let's Encrypt SSL
|
||||
- External Traefik network
|
||||
- Optimized builds
|
||||
|
||||
## SSL/TLS
|
||||
|
||||
### Development
|
||||
Self-signed certificates or HTTP only.
|
||||
|
||||
### Production
|
||||
Automatic Let's Encrypt certificates via Traefik.
|
||||
|
||||
```bash
|
||||
# Traefik labels for SSL
|
||||
traefik.http.routers.api.tls=true
|
||||
traefik.http.routers.api.tls.certresolver=letsencrypt
|
||||
```
|
||||
|
||||
## Health Checks
|
||||
|
||||
All services include health checks:
|
||||
|
||||
```yaml
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
```
|
||||
|
||||
## Backup and Restore
|
||||
|
||||
### Database Backup
|
||||
|
||||
```bash
|
||||
# Automated daily backup
|
||||
docker compose exec postgres pg_dump -U headquarter headquarter > backup.sql
|
||||
|
||||
# Restore
|
||||
docker compose exec -T postgres psql -U headquarter < backup.sql
|
||||
```
|
||||
|
||||
### Repository Backup
|
||||
|
||||
```bash
|
||||
# Backup repos volume
|
||||
docker run --rm -v headquarter_repo_data:/data -v $(pwd):/backup alpine tar czf /backup/repos.tar.gz /data
|
||||
|
||||
# Restore
|
||||
docker run --rm -v headquarter_repo_data:/data -v $(pwd):/backup alpine tar xzf /backup/repos.tar.gz -C /
|
||||
```
|
||||
|
||||
## Monitoring
|
||||
|
||||
### Logs
|
||||
|
||||
```bash
|
||||
# All services
|
||||
docker compose logs -f
|
||||
|
||||
# Specific service
|
||||
docker compose logs -f api
|
||||
|
||||
# Last 100 lines
|
||||
docker compose logs --tail=100 api
|
||||
```
|
||||
|
||||
### Resource Usage
|
||||
|
||||
```bash
|
||||
# Container stats
|
||||
docker stats
|
||||
|
||||
# Disk usage
|
||||
docker system df -v
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
**Database connection failed**
|
||||
```bash
|
||||
# Check PostgreSQL is running
|
||||
docker compose ps
|
||||
|
||||
# Check logs
|
||||
docker compose logs postgres
|
||||
|
||||
# Verify credentials in .env match
|
||||
```
|
||||
|
||||
**Migrations failing**
|
||||
```bash
|
||||
# Check current migration version
|
||||
docker compose exec api alembic current
|
||||
|
||||
# Manual upgrade
|
||||
docker compose exec api alembic upgrade head
|
||||
```
|
||||
|
||||
**Permission denied on repos**
|
||||
```bash
|
||||
# Fix permissions
|
||||
docker compose exec api chown -R appuser:appuser /data/repos
|
||||
```
|
||||
|
||||
## Updates
|
||||
|
||||
### Rolling Update
|
||||
|
||||
```bash
|
||||
# Pull latest images
|
||||
docker compose pull
|
||||
|
||||
# Restart with new images
|
||||
docker compose up -d
|
||||
|
||||
# Run migrations if needed
|
||||
docker compose exec api alembic upgrade head
|
||||
```
|
||||
|
||||
### Zero-Downtime Update
|
||||
|
||||
```bash
|
||||
# Scale API to 2 instances
|
||||
docker compose up -d --scale api=2
|
||||
|
||||
# Update one instance at a time
|
||||
# (Requires load balancer configuration)
|
||||
```
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
1. **Use strong passwords** for database and session secret
|
||||
2. **Enable HTTPS** in production
|
||||
3. **Keep images updated** with security patches
|
||||
4. **Use read-only volumes** where possible
|
||||
5. **Limit container capabilities**
|
||||
6. **Use secrets management** for sensitive data
|
||||
|
||||
```yaml
|
||||
# Example security hardening
|
||||
api:
|
||||
read_only: true
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
cap_drop:
|
||||
- ALL
|
||||
cap_add:
|
||||
- CHOWN
|
||||
- SETGID
|
||||
- SETUID
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- [Docker Documentation](https://docs.docker.com/)
|
||||
- [Docker Compose Reference](https://docs.docker.com/compose/)
|
||||
- [Traefik Documentation](https://doc.traefik.io/traefik/)
|
||||
@@ -0,0 +1,268 @@
|
||||
# Environment Variables Reference
|
||||
|
||||
## Overview
|
||||
|
||||
This document describes all environment variables used by Headquarter. Variables are categorized by component and purpose.
|
||||
|
||||
## Required Variables
|
||||
|
||||
These variables must be set for the application to function:
|
||||
|
||||
### Domains
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `API_DOMAIN` | `localhost` | API server domain (e.g., `api.example.com`) |
|
||||
| `WEB_DOMAIN` | `localhost` | Web frontend domain (e.g., `app.example.com`) |
|
||||
|
||||
### Database
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `POSTGRES_USER` | `headquarter` | PostgreSQL username |
|
||||
| `POSTGRES_PASSWORD` | `headquarter` | PostgreSQL password (change in production!) |
|
||||
| `POSTGRES_DB` | `headquarter` | PostgreSQL database name |
|
||||
|
||||
### Authentication
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `AUTHENTIK_DOMAIN` | `authentik.local` | Authentik server domain |
|
||||
| `AUTHENTIK_CLIENT_ID` | `headquarter-web` | OAuth client ID from Authentik |
|
||||
| `AUTHENTIK_CLIENT_SECRET` | `change-me` | OAuth client secret (change immediately!) |
|
||||
| `AUTHENTIK_APPLICATION_SLUG` | `headquarter-web` | Authentik application slug |
|
||||
| `SESSION_SECRET` | `change-me-session-secret` | Secret for signing session cookies (min 32 chars) |
|
||||
|
||||
## Optional Variables
|
||||
|
||||
### Application
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `APP_ENV` | `development` | Environment: `development`, `staging`, `production` |
|
||||
| `LOG_LEVEL` | `INFO` | Logging level: `DEBUG`, `INFO`, `WARNING`, `ERROR` |
|
||||
|
||||
### Database
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `DATABASE_URL` | (constructed) | Full PostgreSQL connection URL |
|
||||
| `POSTGRES_HOST` | `postgres` | PostgreSQL hostname |
|
||||
| `POSTGRES_PORT` | `5432` | PostgreSQL port |
|
||||
|
||||
When `DATABASE_URL` is not set, it's constructed from:
|
||||
```
|
||||
postgresql+asyncpg://POSTGRES_USER:POSTGRES_PASSWORD@POSTGRES_HOST:POSTGRES_PORT/POSTGRES_DB
|
||||
```
|
||||
|
||||
### URLs
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `API_PUBLIC_URL` | (constructed) | Public API URL (e.g., `https://api.example.com`) |
|
||||
| `WEB_PUBLIC_URL` | (constructed) | Public web URL (e.g., `https://app.example.com`) |
|
||||
|
||||
Constructed from domains when not set:
|
||||
```
|
||||
API: https://API_DOMAIN (production) or http://API_DOMAIN:8000 (development)
|
||||
WEB: https://WEB_DOMAIN (production) or http://WEB_DOMAIN:5173 (development)
|
||||
```
|
||||
|
||||
### Authentik Overrides
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `AUTHENTIK_AUTHORIZE_URL` | (constructed) | OAuth authorization endpoint |
|
||||
| `AUTHENTIK_TOKEN_URL` | (constructed) | OAuth token endpoint |
|
||||
| `AUTHENTIK_JWKS_URL` | (constructed) | JWKS endpoint |
|
||||
| `AUTHENTIK_ISSUER` | (constructed) | OAuth issuer URL |
|
||||
| `AUTHENTIK_AUDIENCE` | `headquarter-web` | Token audience |
|
||||
|
||||
Constructed URLs:
|
||||
```
|
||||
https://AUTHENTIK_DOMAIN/application/o/authorize/
|
||||
https://AUTHENTIK_DOMAIN/application/o/token/
|
||||
https://AUTHENTIK_DOMAIN/application/o/AUTHENTIK_APPLICATION_SLUG/jwks/
|
||||
https://AUTHENTIK_DOMAIN/application/o/AUTHENTIK_APPLICATION_SLUG/
|
||||
```
|
||||
|
||||
### Session
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `SESSION_TTL_HOURS` | `24` | Session cookie lifetime in hours |
|
||||
| `COOKIE_DOMAIN` | (none) | Cookie domain (set for cross-subdomain) |
|
||||
| `COOKIE_SECURE` | `true` (prod) | Secure cookie flag |
|
||||
| `COOKIE_SAMESITE` | `lax` | SameSite cookie attribute |
|
||||
|
||||
### Storage
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `REPO_BASE_PATH` | `/data/repos` | Base path for git repositories |
|
||||
| `UPLOAD_DIR` | `uploads` | Directory for file uploads |
|
||||
|
||||
### Development
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `VITE_API_BASE_URL` | `http://localhost:8000` | Frontend API URL |
|
||||
|
||||
## Docker Compose Variables
|
||||
|
||||
### Traefik
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `TRAEFIK_NETWORK` | `traefik` | Docker network name for Traefik |
|
||||
| `TRAEFIK_CERT_RESOLVER` | `letsencrypt` | Certificate resolver name |
|
||||
|
||||
### Docker Specific
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `COMPOSE_PROJECT_NAME` | `headquarter` | Docker Compose project name |
|
||||
|
||||
## Configuration Examples
|
||||
|
||||
### Development
|
||||
|
||||
```bash
|
||||
APP_ENV=development
|
||||
API_DOMAIN=localhost
|
||||
WEB_DOMAIN=localhost
|
||||
DATABASE_URL=postgresql+asyncpg://headquarter:headquarter@localhost:5432/headquarter
|
||||
AUTHENTIK_DOMAIN=authentik.local
|
||||
AUTHENTIK_CLIENT_ID=headquarter-web
|
||||
AUTHENTIK_CLIENT_SECRET=dev-secret
|
||||
SESSION_SECRET=dev-session-secret-change-in-production
|
||||
VITE_API_BASE_URL=http://localhost:8000
|
||||
```
|
||||
|
||||
### Production
|
||||
|
||||
```bash
|
||||
APP_ENV=production
|
||||
API_DOMAIN=api.headquarter.example.com
|
||||
WEB_DOMAIN=app.headquarter.example.com
|
||||
POSTGRES_PASSWORD=very-secure-password-here
|
||||
AUTHENTIK_DOMAIN=auth.example.com
|
||||
AUTHENTIK_CLIENT_ID=your-uuid-from-authentik
|
||||
AUTHENTIK_CLIENT_SECRET=your-secret-from-authentik
|
||||
AUTHENTIK_APPLICATION_SLUG=headquarter-web
|
||||
SESSION_SECRET=minimum-32-characters-long-secret-key
|
||||
SESSION_TTL_HOURS=24
|
||||
COOKIE_DOMAIN=.headquarter.example.com
|
||||
TRAEFIK_NETWORK=web
|
||||
TRAEFIK_CERT_RESOLVER=letsencrypt
|
||||
REPO_BASE_PATH=/data/repos
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
```bash
|
||||
APP_ENV=testing
|
||||
DATABASE_URL=postgresql+asyncpg://headquarter:headquarter@localhost:5432/headquarter_test
|
||||
SESSION_SECRET=test-secret
|
||||
AUTHENTIK_CLIENT_ID=test-client
|
||||
AUTHENTIK_CLIENT_SECRET=test-secret
|
||||
```
|
||||
|
||||
## Security Checklist
|
||||
|
||||
Before deploying to production, verify:
|
||||
|
||||
- [ ] `POSTGRES_PASSWORD` is strong and unique
|
||||
- [ ] `AUTHENTIK_CLIENT_SECRET` is kept secret
|
||||
- [ ] `SESSION_SECRET` is at least 32 characters
|
||||
- [ ] `APP_ENV` is set to `production`
|
||||
- [ ] `COOKIE_SECURE` is enabled
|
||||
- [ ] `COOKIE_DOMAIN` is set for your domain
|
||||
- [ ] No default secrets in production
|
||||
- [ ] `.env` file is not committed to git
|
||||
- [ ] `.env` file has restricted permissions (600)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Variable Not Set
|
||||
|
||||
```bash
|
||||
# Error: "Environment variable not set"
|
||||
# Solution: Export the variable or add to .env file
|
||||
export SESSION_SECRET="your-secret-here"
|
||||
```
|
||||
|
||||
### Invalid URL
|
||||
|
||||
```bash
|
||||
# Error: "Invalid URL"
|
||||
# Solution: Check domain variables don't include protocol
|
||||
# Bad: API_DOMAIN=https://api.example.com
|
||||
# Good: API_DOMAIN=api.example.com
|
||||
```
|
||||
|
||||
### Database Connection Failed
|
||||
|
||||
```bash
|
||||
# Check DATABASE_URL or individual components
|
||||
# Verify PostgreSQL is running
|
||||
# Check credentials match
|
||||
```
|
||||
|
||||
## Migration from Old Config
|
||||
|
||||
If upgrading from older versions:
|
||||
|
||||
1. `JWT_SECRET` → Removed (not needed with session auth)
|
||||
2. `ACCESS_TOKEN_TTL_MINUTES` → Removed
|
||||
3. `REFRESH_TOKEN_TTL_DAYS` → Removed
|
||||
4. `AUTHENTIK_AUDIENCE` → Now defaults to `AUTHENTIK_CLIENT_ID`
|
||||
5. `AUTHENTIK_APPLICATION_SLUG` → New variable for URL construction
|
||||
|
||||
## Environment Files
|
||||
|
||||
### Files Structure
|
||||
|
||||
```
|
||||
headquarter/
|
||||
├── .env # Main environment (not committed)
|
||||
├── .env.example # Example/template
|
||||
├── apps/
|
||||
│ ├── api/
|
||||
│ │ └── .env # API-specific overrides
|
||||
│ └── web/
|
||||
│ └── .env # Frontend-specific overrides
|
||||
└── docker-compose.traefik.yml # References .env
|
||||
```
|
||||
|
||||
### Loading Order
|
||||
|
||||
1. System environment variables
|
||||
2. `.env` file in project root
|
||||
3. Component-specific `.env` files
|
||||
4. Default values in code
|
||||
|
||||
Later values override earlier ones.
|
||||
|
||||
## Validation
|
||||
|
||||
The application validates required variables on startup:
|
||||
|
||||
```python
|
||||
# Missing critical variable
|
||||
if not settings.session_secret or settings.session_secret == "change-me":
|
||||
logger.warning("SESSION_SECRET not configured properly!")
|
||||
|
||||
# Invalid configuration
|
||||
if settings.app_env == "production" and "localhost" in settings.api_domain:
|
||||
logger.warning("Using localhost in production!")
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Never commit `.env` files**
|
||||
2. **Use strong passwords** for database and secrets
|
||||
3. **Rotate secrets** regularly
|
||||
4. **Use different secrets** per environment
|
||||
5. **Document custom variables** in this file
|
||||
6. **Validate configuration** before deployment
|
||||
@@ -0,0 +1,305 @@
|
||||
# Traefik Reverse Proxy Setup
|
||||
|
||||
## Overview
|
||||
|
||||
Traefik serves as the reverse proxy and load balancer for Headquarter in production, handling:
|
||||
- SSL/TLS termination
|
||||
- Automatic HTTPS via Let's Encrypt
|
||||
- Route-based traffic distribution
|
||||
- WebSocket support
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Internet
|
||||
↓
|
||||
Traefik (443)
|
||||
├───▶ api.yourdomain.com → Headquarter API (8000)
|
||||
└───▶ app.yourdomain.com → Headquarter Web (80)
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Docker Compose installed
|
||||
- DNS records pointing to your server:
|
||||
- `api.yourdomain.com` → Server IP
|
||||
- `app.yourdomain.com` → Server IP
|
||||
- Ports 80 and 443 open in firewall
|
||||
|
||||
## Configuration
|
||||
|
||||
### 1. Environment Variables
|
||||
|
||||
```bash
|
||||
# .env file
|
||||
API_DOMAIN=api.headquarter.example.com
|
||||
WEB_DOMAIN=app.headquarter.example.com
|
||||
|
||||
# Traefik network (shared with other Traefik instances)
|
||||
TRAEFIK_NETWORK=web
|
||||
|
||||
# SSL Certificate resolver
|
||||
TRAEFIK_CERT_RESOLVER=letsencrypt
|
||||
```
|
||||
|
||||
### 2. Traefik Labels
|
||||
|
||||
Services are configured via Docker labels:
|
||||
|
||||
```yaml
|
||||
# API Service labels
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.headquarter-api.rule=Host(`api.headquarter.example.com`)"
|
||||
- "traefik.http.routers.headquarter-api.entrypoints=websecure"
|
||||
- "traefik.http.routers.headquarter-api.tls=true"
|
||||
- "traefik.http.routers.headquarter-api.tls.certresolver=${TRAEFIK_CERT_RESOLVER:-letsencrypt}"
|
||||
- "traefik.http.services.headquarter-api.loadbalancer.server.port=8000"
|
||||
|
||||
# Web Service labels
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.headquarter-frontend.rule=Host(`app.headquarter.example.com`)"
|
||||
- "traefik.http.routers.headquarter-frontend.entrypoints=websecure"
|
||||
- "traefik.http.routers.headquarter-frontend.tls=true"
|
||||
- "traefik.http.routers.headquarter-frontend.tls.certresolver=${TRAEFIK_CERT_RESOLVER:-letsencrypt}"
|
||||
- "traefik.http.services.headquarter-frontend.loadbalancer.server.port=80"
|
||||
```
|
||||
|
||||
### 3. External Network
|
||||
|
||||
Connect to existing Traefik instance:
|
||||
|
||||
```yaml
|
||||
networks:
|
||||
traefik:
|
||||
external: true
|
||||
name: ${TRAEFIK_NETWORK:-traefik}
|
||||
```
|
||||
|
||||
## Deployment Steps
|
||||
|
||||
### Step 1: Verify DNS
|
||||
|
||||
Ensure DNS records resolve to your server:
|
||||
|
||||
```bash
|
||||
nslookup api.headquarter.example.com
|
||||
nslookup app.headquarter.example.com
|
||||
```
|
||||
|
||||
### Step 2: Start Services
|
||||
|
||||
```bash
|
||||
# Start with Traefik compose file
|
||||
docker compose -f docker-compose.traefik.yml up -d
|
||||
|
||||
# Verify containers are running
|
||||
docker compose -f docker-compose.traefik.yml ps
|
||||
```
|
||||
|
||||
### Step 3: Check SSL Certificates
|
||||
|
||||
```bash
|
||||
# View Traefik logs
|
||||
docker compose -f docker-compose.traefik.yml logs -f
|
||||
|
||||
# Check certificate status
|
||||
curl -v https://api.headquarter.example.com/health
|
||||
```
|
||||
|
||||
### Step 4: Run Migrations
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.traefik.yml exec api alembic upgrade head
|
||||
```
|
||||
|
||||
## SSL Configuration
|
||||
|
||||
### Let's Encrypt (Default)
|
||||
|
||||
Automatic certificate generation and renewal:
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
- "traefik.http.routers.headquarter-api.tls.certresolver=letsencrypt"
|
||||
```
|
||||
|
||||
### Custom Certificates
|
||||
|
||||
For custom or wildcard certificates:
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
- "traefik.http.routers.headquarter-api.tls=true"
|
||||
- "traefik.http.routers.headquarter-api.tls.certresolver=myresolver"
|
||||
```
|
||||
|
||||
### Self-Signed (Development)
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
- "traefik.http.routers.headquarter-api.tls=true"
|
||||
- "traefik.http.routers.headquarter-api.tls.certresolver=selfsigned"
|
||||
```
|
||||
|
||||
## Advanced Configuration
|
||||
|
||||
### Rate Limiting
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
- "traefik.http.middlewares.ratelimit.ratelimit.average=100"
|
||||
- "traefik.http.routers.headquarter-api.middlewares=ratelimit"
|
||||
```
|
||||
|
||||
### Basic Auth (for staging)
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
- "traefik.http.middlewares.auth.basicauth.users=admin:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/"
|
||||
- "traefik.http.routers.headquarter-api.middlewares=auth"
|
||||
```
|
||||
|
||||
### CORS Headers
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
- "traefik.http.middlewares.cors.headers.accesscontrolalloworiginlist=*"
|
||||
- "traefik.http.routers.headquarter-api.middlewares=cors"
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Certificate Issues
|
||||
|
||||
**Problem**: Certificate not generated
|
||||
```bash
|
||||
# Check Traefik logs
|
||||
docker compose logs traefik
|
||||
|
||||
# Verify DNS resolution
|
||||
nslookup your-domain.com
|
||||
|
||||
# Check port 80 is accessible (required for HTTP challenge)
|
||||
curl -I http://your-domain.com
|
||||
```
|
||||
|
||||
**Problem**: Certificate expired
|
||||
```bash
|
||||
# Force renewal
|
||||
docker compose restart traefik
|
||||
|
||||
# Or delete acme.json and restart
|
||||
rm acme.json
|
||||
docker compose restart traefik
|
||||
```
|
||||
|
||||
### Routing Issues
|
||||
|
||||
**Problem**: 404 errors
|
||||
```bash
|
||||
# Check Traefik dashboard (if enabled)
|
||||
# http://traefik.yourdomain.com
|
||||
|
||||
# Verify labels are correct
|
||||
docker compose -f docker-compose.traefik.yml config
|
||||
|
||||
# Check container is on correct network
|
||||
docker network inspect ${TRAEFIK_NETWORK:-traefik}
|
||||
```
|
||||
|
||||
**Problem**: Services not detected
|
||||
```bash
|
||||
# Verify traefik.enable label
|
||||
docker compose -f docker-compose.traefik.yml exec api labels
|
||||
|
||||
# Check Docker provider in Traefik
|
||||
docker compose logs traefik | grep "Provider connection established"
|
||||
```
|
||||
|
||||
## Maintenance
|
||||
|
||||
### Update Traefik
|
||||
|
||||
```bash
|
||||
# Pull latest Traefik image
|
||||
docker compose -f docker-compose.traefik.yml pull traefik
|
||||
|
||||
# Restart
|
||||
docker compose -f docker-compose.traefik.yml up -d traefik
|
||||
```
|
||||
|
||||
### View Dashboard
|
||||
|
||||
Enable Traefik dashboard (secure it in production):
|
||||
|
||||
```yaml
|
||||
# traefik.yml
|
||||
dashboard:
|
||||
enabled: true
|
||||
|
||||
# Add to docker-compose.traefik.yml
|
||||
labels:
|
||||
- "traefik.http.routers.dashboard.rule=Host(`traefik.yourdomain.com`)"
|
||||
- "traefik.http.routers.dashboard.tls=true"
|
||||
- "traefik.http.routers.dashboard.tls.certresolver=letsencrypt"
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
1. **Always use HTTPS** in production (redirect HTTP to HTTPS)
|
||||
2. **Secure Traefik dashboard** with authentication
|
||||
3. **Use strong certificate resolver** (Let's Encrypt production)
|
||||
4. **Keep Traefik updated** for security patches
|
||||
5. **Restrict Docker socket access** if using Docker provider
|
||||
|
||||
## Example Complete Configuration
|
||||
|
||||
```yaml
|
||||
# docker-compose.traefik.yml
|
||||
services:
|
||||
api:
|
||||
build:
|
||||
context: ./apps/api
|
||||
environment:
|
||||
API_DOMAIN: ${API_DOMAIN}
|
||||
WEB_DOMAIN: ${WEB_DOMAIN}
|
||||
# ... other env vars
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.headquarter-api.rule=Host(`${API_DOMAIN}`)"
|
||||
- "traefik.http.routers.headquarter-api.entrypoints=websecure"
|
||||
- "traefik.http.routers.headquarter-api.tls=true"
|
||||
- "traefik.http.routers.headquarter-api.tls.certresolver=${TRAEFIK_CERT_RESOLVER:-letsencrypt}"
|
||||
- "traefik.http.services.headquarter-api.loadbalancer.server.port=8000"
|
||||
networks:
|
||||
- backend
|
||||
- traefik
|
||||
|
||||
web:
|
||||
build:
|
||||
context: ./apps/web
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.headquarter-frontend.rule=Host(`${WEB_DOMAIN}`)"
|
||||
- "traefik.http.routers.headquarter-frontend.entrypoints=websecure"
|
||||
- "traefik.http.routers.headquarter-frontend.tls=true"
|
||||
- "traefik.http.routers.headquarter-frontend.tls.certresolver=${TRAEFIK_CERT_RESOLVER:-letsencrypt}"
|
||||
- "traefik.http.services.headquarter-frontend.loadbalancer.server.port=80"
|
||||
networks:
|
||||
- traefik
|
||||
|
||||
networks:
|
||||
traefik:
|
||||
external: true
|
||||
name: ${TRAEFIK_NETWORK:-traefik}
|
||||
backend:
|
||||
internal: true
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- [Traefik Documentation](https://doc.traefik.io/traefik/)
|
||||
- [Docker Compose Integration](https://doc.traefik.io/traefik/providers/docker/)
|
||||
- [Let's Encrypt Configuration](https://doc.traefik.io/traefik/https/acme/)
|
||||
Reference in New Issue
Block a user