# Deployment Guide This document covers deploying Tatlock UI to the Tower of Joy infrastructure. ## Overview ``` Internet → NPM (home.schweitz.net:443) → tatlock-ui container (port 8092) → Flutter web ``` ## Web Build ### Local Build ```bash # Generate version info dart run tool/generate_version.dart # Build for web release flutter build web --release ``` Build output: `build/web/` ### Build Optimizations For production builds, consider: ```bash flutter build web --release \ --dart-define=FLUTTER_WEB_USE_SKIA=true \ --tree-shake-icons ``` ## Docker ### Dockerfile ```dockerfile # Build stage FROM ghcr.io/cirruslabs/flutter:stable AS build WORKDIR /app # Copy dependency files COPY pubspec.* ./ RUN flutter pub get # Copy source and build COPY . . RUN dart run tool/generate_version.dart RUN flutter build web --release # Production stage FROM nginx:alpine # Copy built web app COPY --from=build /app/build/web /usr/share/nginx/html # Copy nginx config (optional) COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 CMD ["nginx", "-g", "daemon off;"] ``` ### nginx.conf (Optional) For SPA routing support: ```nginx server { listen 80; server_name _; root /usr/share/nginx/html; index index.html; # Gzip compression gzip on; gzip_types text/plain text/css application/json application/javascript text/xml application/xml; # SPA routing - serve index.html for all routes location / { try_files $uri $uri/ /index.html; } # Cache static assets location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ { expires 1y; add_header Cache-Control "public, immutable"; } # Don't cache index.html location = /index.html { expires -1; add_header Cache-Control "no-store, no-cache, must-revalidate"; } } ``` ### Build Image Locally ```bash docker build -t tatlock-ui:latest . # Test locally docker run -p 8092:80 tatlock-ui:latest ``` ## Portainer Stack ### docker-compose.yml ```yaml version: '3.8' services: tatlock-ui: image: git.schweitz.net/jpmschweitzer/tatlock-ui:latest container_name: tatlock-ui restart: unless-stopped ports: - "8092:80" networks: - docker-dataplane labels: - "com.centurylinklabs.watchtower.enable=true" networks: docker-dataplane: external: true ``` ### Deploy to Portainer 1. Go to Portainer: https://portainer.schweitz.net 2. Navigate to Stacks → Add Stack 3. Name: `tatlock-ui` 4. Paste docker-compose.yml content 5. Deploy ## Nginx Proxy Manager ### Proxy Host Configuration | Setting | Value | |---------|-------| | Domain | `home.schweitz.net` | | Scheme | `http` | | Forward Hostname | `tatlock-ui` (container name) | | Forward Port | `80` | ### SSL | Setting | Value | |---------|-------| | SSL Certificate | Let's Encrypt | | Force SSL | Yes | | HTTP/2 | Yes | | HSTS | Yes | ### Advanced (Custom Nginx) ```nginx # WebSocket support (if needed for future features) location /ws { proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } ``` ## CI/CD Pipeline ### Gitea Actions Workflow Create `.gitea/workflows/build.yml`: ```yaml name: Build and Deploy on: push: branches: [main, master] pull_request: branches: [main, master] jobs: test: runs-on: ubuntu-latest container: image: ghcr.io/cirruslabs/flutter:stable steps: - uses: actions/checkout@v4 - name: Get dependencies run: flutter pub get - name: Generate version run: dart run tool/generate_version.dart - name: Analyze run: flutter analyze - name: Test run: flutter test build: needs: test runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' steps: - uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to Gitea Registry uses: docker/login-action@v3 with: registry: git.schweitz.net username: ${{ secrets.REGISTRY_USER }} password: ${{ secrets.REGISTRY_TOKEN }} - name: Build and push uses: docker/build-push-action@v5 with: context: . push: true tags: git.schweitz.net/jpmschweitzer/tatlock-ui:latest deploy: needs: build runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' steps: - name: Trigger Watchtower run: | curl -X POST "http://watchtower:8080/v1/update" \ -H "Authorization: Bearer ${{ secrets.WATCHTOWER_TOKEN }}" ``` ## Environment Configuration ### Runtime Configuration For environment-specific settings, use compile-time defines: ```bash flutter build web --release \ --dart-define=API_URL=https://api.schweitz.net \ --dart-define=AUTH_URL=https://auth.schweitz.net ``` Access in code: ```dart class AppConfig { static const apiUrl = String.fromEnvironment( 'API_URL', defaultValue: 'https://api.schweitz.net', ); static const authUrl = String.fromEnvironment( 'AUTH_URL', defaultValue: 'https://auth.schweitz.net', ); } ``` ## Health Monitoring ### Container Health Check Add to Dockerfile: ```dockerfile HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD curl -f http://localhost/ || exit 1 ``` ### Uptime Monitoring Add to your monitoring solution (Uptime Kuma, etc.): - URL: `https://home.schweitz.net` - Interval: 60s - Expected status: 200 ## Rollback ### Quick Rollback If issues arise after deployment: ```bash # In Portainer, update image tag to previous version git.schweitz.net/jpmschweitzer/tatlock-ui:previous-tag # Or via CLI docker pull git.schweitz.net/jpmschweitzer/tatlock-ui:v0.1.0 docker stop tatlock-ui docker rm tatlock-ui docker run -d --name tatlock-ui -p 8092:80 git.schweitz.net/jpmschweitzer/tatlock-ui:v0.1.0 ``` ## Troubleshooting ### Container won't start ```bash # Check logs docker logs tatlock-ui # Common issues: # - Port 8092 already in use # - Network not found (create docker-dataplane network) ``` ### 502 Bad Gateway in NPM 1. Verify container is running: `docker ps | grep tatlock-ui` 2. Check container is on correct network 3. Verify port mapping in docker-compose ### Assets not loading Check nginx is serving from correct path and CORS headers if loading from different domain.