Files
jpmschweitzerandClaude Opus 4.5 0c3239f4b0 docs: add Phase 0 documentation
- docs/ARCHITECTURE.md - Clean Architecture patterns and conventions
- docs/API_INTEGRATION.md - Core API and Tatlock API endpoints
- docs/DEPLOYMENT.md - Docker, NPM, Portainer setup
- docs/DATAGRID.md - DataGrid component specification
- docs/THEMING.md - Material 3 theming guide

Also update seed color to teal for consistency.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 16:26:44 +01:00

6.4 KiB

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

# 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:

flutter build web --release \
  --dart-define=FLUTTER_WEB_USE_SKIA=true \
  --tree-shake-icons

Docker

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:

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

docker build -t tatlock-ui:latest .

# Test locally
docker run -p 8092:80 tatlock-ui:latest

Portainer Stack

docker-compose.yml

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)

# 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:

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:

flutter build web --release \
  --dart-define=API_URL=https://api.schweitz.net \
  --dart-define=AUTH_URL=https://auth.schweitz.net

Access in code:

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:

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:

# 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

# 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.