- 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>
329 lines
6.4 KiB
Markdown
329 lines
6.4 KiB
Markdown
# 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.
|