Skip to content

Production Setup

Configure Tempo for production deployment with best practices.

Overview

This guide covers production configuration, environment variables, and deployment considerations.

Environment Variables

Configure the following in docker-compose.prod.yml or via environment files:

Required Configuration

JWT Secret Key

CRITICAL: Must be set to a secure random value.

# Generate a secure key
openssl rand -base64 32

Set in docker-compose.prod.yml:

JWT__SecretKey: "your-generated-secret-key-here"

Or use an environment variable:

export JWT_SECRET_KEY="your-generated-secret-key-here"

Database Configuration

ConnectionStrings__DefaultConnection: "Host=postgres;Port=5432;Database=tempo;Username=postgres;Password=YOUR_SECURE_PASSWORD"

Important: Change the default database password in production.

CORS Configuration

Configure allowed origins:

CORS__AllowedOrigins: "https://yourdomain.com,https://www.yourdomain.com"

For multiple origins, use comma-separated list.

Media Storage

MediaStorage__RootPath: "/app/media"
MediaStorage__MaxFileSizeBytes: "52428800"  # 50MB default

Elevation Calculation

ElevationCalculation__NoiseThresholdMeters: "2.0"
ElevationCalculation__MinDistanceMeters: "10.0"

JWT Configuration

JWT__SecretKey: "YOUR_SECRET_KEY"  # REQUIRED
JWT__Issuer: "Tempo"
JWT__Audience: "Tempo"
JWT__ExpirationDays: "7"

Production Checklist

Security

  • JWT secret key is set and secure (minimum 32 characters)
  • Database password changed from default
  • HTTPS configured (required for secure cookies)
  • CORS origins configured correctly
  • Firewall rules configured
  • Regular security updates applied

Configuration

  • Environment variables set correctly
  • Database connection string configured
  • Media storage path configured and writable
  • Ports configured appropriately
  • Health checks enabled

Data Management

  • Backup strategy in place
  • Media directory included in backups
  • Database backup automated
  • Sufficient disk space allocated

Monitoring

  • Health check endpoint accessible
  • Logs configured and monitored
  • Resource usage monitored
  • Error tracking in place

Reverse Proxy Setup

Nginx Example

server {
    listen 80;
    server_name yourdomain.com;

    # Redirect to HTTPS
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name yourdomain.com;

    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;

    # Frontend
    location / {
        proxy_pass http://localhost:3004;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    # API
    location /api/ {
        proxy_pass http://localhost:5001/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Large file upload support (bulk imports up to 500MB)
        client_max_body_size 500M;
        proxy_read_timeout 600s;
        proxy_connect_timeout 600s;
        proxy_send_timeout 600s;
    }
}

Caddy Example

# API endpoint for iOS app (direct API access)
api-tempo.yourdomain.com {
    tls {
        protocols tls1.2 tls1.3
    }

    # Large file uploads (bulk imports up to 500MB)
    reverse_proxy localhost:5001 {
        transport http {
            read_timeout 30m
            write_timeout 30m
        }
    }
}

# Web frontend with API routing
tempo.yourdomain.com {
    tls {
        protocols tls1.2 tls1.3
    }

    # Large file uploads (bulk imports) - route to API (strip only /api prefix)
    @large_upload {
        path /api/workouts/import/*
    }
    handle @large_upload {
        uri strip_prefix /api
        reverse_proxy localhost:5001 {
            transport http {
                read_timeout 30m
                write_timeout 30m
            }
            header_up Host {host}
            header_up X-Real-IP {remote}
            header_up X-Forwarded-For {remote_host}
            header_up X-Forwarded-Proto {scheme}
        }
    }

    # Other API routes (strip only /api prefix)
    handle_path /api/* {
        reverse_proxy localhost:5001 {
            header_up Host {host}
            header_up X-Real-IP {remote}
            header_up X-Forwarded-For {remote_host}
            header_up X-Forwarded-Proto {scheme}
        }
    }

    # Frontend
    handle {
        reverse_proxy localhost:3004 {
            header_up Host {host}
            header_up X-Real-IP {remote}
            header_up X-Forwarded-For {remote_host}
            header_up X-Forwarded-Proto {scheme}
        }
    }
}

Note: - Adjust the hostnames and ports based on your deployment setup - The api-tempo.yourdomain.com subdomain is optional and only needed if you have a mobile app that requires direct API access - The uri strip_prefix /api directive ensures that /api/workouts/import/bulk is forwarded to the API as /workouts/import/bulk (stripping only the /api prefix)

Traefik Example

labels:
  - "traefik.enable=true"
  - "traefik.http.routers.tempo.rule=Host(`yourdomain.com`)"
  - "traefik.http.routers.tempo.entrypoints=websecure"
  - "traefik.http.routers.tempo.tls.certresolver=letsencrypt"
  # Large file upload support
  - "traefik.http.middlewares.tempo-buffering.buffering.maxRequestBodyBytes=524288000"  # 500MB
  - "traefik.http.middlewares.tempo-timeout.buffering.retryExpression=IsNetworkError() && Attempts() < 2"
  - "traefik.http.routers.tempo.middlewares=tempo-buffering,tempo-timeout"

Large File Upload Requirements

Tempo supports bulk imports of up to 500MB (Strava exports, Tempo exports). Your reverse proxy must be configured to handle these large uploads:

Required Settings

  • Maximum body size: 500MB minimum
  • Read timeout: 10-30 minutes (depending on expected upload speed)
  • Write timeout: 10-30 minutes
  • Connect timeout: 10 minutes

These settings are already configured in the API (Program.cs) and only need to be set at the reverse proxy level.

Testing Large Uploads

After configuration, test with a large file: 1. Export your data from Strava (typically 50-200MB) 2. Import via Settings > Import > Bulk Strava Import 3. Monitor reverse proxy logs for timeout errors

SSL/TLS Configuration

Let's Encrypt (Certbot)

# Install Certbot
sudo apt-get install certbot python3-certbot-nginx

# Obtain certificate
sudo certbot --nginx -d yourdomain.com

# Auto-renewal
sudo certbot renew --dry-run

Self-Signed Certificate (Development Only)

openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout key.pem -out cert.pem

Note: Self-signed certificates are not recommended for production.

Performance Optimization

Resource Limits

Configure Docker resource limits:

deploy:
  resources:
    limits:
      cpus: '2'
      memory: 4G
    reservations:
      cpus: '1'
      memory: 2G

Database Optimization

  • Configure PostgreSQL shared_buffers
  • Set appropriate max_connections
  • Enable query logging for optimization
  • Regular VACUUM and ANALYZE

Media Storage

  • Use fast storage for media directory
  • Consider object storage for large deployments
  • Implement cleanup for old media files

Monitoring and Logging

Health Checks

Monitor the health endpoint:

curl http://localhost:5001/health

Logging

View container logs:

docker-compose -f docker-compose.prod.yml logs -f api
docker-compose -f docker-compose.prod.yml logs -f frontend

Resource Monitoring

Monitor resource usage:

docker stats

Next Steps