feat: initial commit - Supervisor UI with Next.js 16 and Tailwind CSS 4
Some checks failed
Build and Push Docker Image to Gitea / build-and-push (push) Failing after 1m22s
Some checks failed
Build and Push Docker Image to Gitea / build-and-push (push) Failing after 1m22s
- Modern web interface for Supervisor process management - Built with Next.js 16 (App Router) and Tailwind CSS 4 - Full XML-RPC client implementation for Supervisor API - Real-time process monitoring with auto-refresh - Process control: start, stop, restart operations - Modern dashboard with system status and statistics - Dark/light theme with OKLCH color system - Docker multi-stage build with runtime env var configuration - Gitea CI/CD workflow for automated builds - Comprehensive documentation (README, IMPLEMENTATION, DEPLOYMENT) Features: - Backend proxy pattern for secure API communication - React Query for state management and caching - TypeScript strict mode with Zod validation - Responsive design with mobile support - Health check endpoint for monitoring - Non-root user security in Docker Environment Variables: - SUPERVISOR_HOST, SUPERVISOR_PORT - SUPERVISOR_USERNAME, SUPERVISOR_PASSWORD (optional) - Configurable at build-time and runtime 🤖 Generated with Claude Code (https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
550
DEPLOYMENT.md
Normal file
550
DEPLOYMENT.md
Normal file
@@ -0,0 +1,550 @@
|
||||
# Deployment Guide - Supervisor UI
|
||||
|
||||
## Environment Variables
|
||||
|
||||
The Supervisor UI supports flexible configuration through environment variables that can be set at both build-time and runtime.
|
||||
|
||||
### Available Environment Variables
|
||||
|
||||
| Variable | Description | Default | Required |
|
||||
|----------|-------------|---------|----------|
|
||||
| `SUPERVISOR_HOST` | Supervisor API host/IP address | `localhost` | Yes |
|
||||
| `SUPERVISOR_PORT` | Supervisor API port | `9001` | Yes |
|
||||
| `SUPERVISOR_USERNAME` | Basic auth username (if enabled) | - | No |
|
||||
| `SUPERVISOR_PASSWORD` | Basic auth password (if enabled) | - | No |
|
||||
| `NODE_ENV` | Node environment | `production` | No |
|
||||
| `PORT` | Application port | `3000` | No |
|
||||
|
||||
### Configuration Priority
|
||||
|
||||
Environment variables can be set at different stages, with runtime taking precedence:
|
||||
|
||||
1. **Runtime** (highest priority) - Docker run `-e` flags or docker-compose environment
|
||||
2. **Build-time** - Docker build `--build-arg` flags
|
||||
3. **Dockerfile defaults** (lowest priority)
|
||||
|
||||
## Docker Build Arguments
|
||||
|
||||
You can customize the build with build arguments:
|
||||
|
||||
```bash
|
||||
docker build -t supervisor-ui \
|
||||
--build-arg SUPERVISOR_HOST=supervisor.example.com \
|
||||
--build-arg SUPERVISOR_PORT=9001 \
|
||||
--build-arg SUPERVISOR_USERNAME=admin \
|
||||
--build-arg SUPERVISOR_PASSWORD=secret \
|
||||
.
|
||||
```
|
||||
|
||||
**Note**: Build arguments are embedded in the image layer. For sensitive credentials, prefer runtime environment variables.
|
||||
|
||||
## Docker Runtime Configuration
|
||||
|
||||
### Option 1: Environment Variables via Docker Run
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name supervisor-ui \
|
||||
-p 3000:3000 \
|
||||
-e SUPERVISOR_HOST=supervisor.example.com \
|
||||
-e SUPERVISOR_PORT=9001 \
|
||||
-e SUPERVISOR_USERNAME=admin \
|
||||
-e SUPERVISOR_PASSWORD=secret \
|
||||
dev.pivoine.art/valknar/supervisor-ui:latest
|
||||
```
|
||||
|
||||
### Option 2: Environment File
|
||||
|
||||
Create an `.env` file:
|
||||
|
||||
```env
|
||||
SUPERVISOR_HOST=supervisor.example.com
|
||||
SUPERVISOR_PORT=9001
|
||||
SUPERVISOR_USERNAME=admin
|
||||
SUPERVISOR_PASSWORD=secret
|
||||
```
|
||||
|
||||
Run with env file:
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name supervisor-ui \
|
||||
-p 3000:3000 \
|
||||
--env-file .env \
|
||||
dev.pivoine.art/valknar/supervisor-ui:latest
|
||||
```
|
||||
|
||||
### Option 3: Docker Compose
|
||||
|
||||
Create a `.env` file in the same directory as `docker-compose.yml`:
|
||||
|
||||
```env
|
||||
SUPERVISOR_HOST=supervisor.example.com
|
||||
SUPERVISOR_PORT=9001
|
||||
SUPERVISOR_USERNAME=admin
|
||||
SUPERVISOR_PASSWORD=secret
|
||||
```
|
||||
|
||||
The `docker-compose.yml` automatically picks up these variables:
|
||||
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### Option 4: Docker Compose with Inline Environment
|
||||
|
||||
Edit `docker-compose.yml`:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
supervisor-ui:
|
||||
image: dev.pivoine.art/valknar/supervisor-ui:latest
|
||||
environment:
|
||||
- SUPERVISOR_HOST=supervisor.example.com
|
||||
- SUPERVISOR_PORT=9001
|
||||
- SUPERVISOR_USERNAME=admin
|
||||
- SUPERVISOR_PASSWORD=secret
|
||||
```
|
||||
|
||||
## CI/CD - Gitea Workflows
|
||||
|
||||
### Automatic Builds
|
||||
|
||||
The project includes a Gitea Actions workflow that automatically builds and pushes Docker images to the Gitea registry.
|
||||
|
||||
**Workflow file**: `.gitea/workflows/docker-build-push.yml`
|
||||
|
||||
### Trigger Events
|
||||
|
||||
Images are built and pushed on:
|
||||
|
||||
1. **Push to main branch** → Tagged as `latest` and `main-<sha>`
|
||||
2. **Push to develop branch** → Tagged as `develop` and `develop-<sha>`
|
||||
3. **Git tags** (v*.*.* pattern) → Tagged as version (e.g., `v1.0.0`, `1.0`, `1`)
|
||||
4. **Pull requests** → Built but not pushed (validation only)
|
||||
5. **Manual workflow dispatch** → Custom tag specified by user
|
||||
|
||||
### Workflow Secrets
|
||||
|
||||
The workflow requires one secret to be configured in your Gitea repository:
|
||||
|
||||
- `REGISTRY_TOKEN` - Gitea personal access token with registry push permissions
|
||||
|
||||
#### Creating the Registry Token
|
||||
|
||||
1. Go to Gitea Settings → Applications → Generate New Token
|
||||
2. Name: `GitHub Actions Registry`
|
||||
3. Select scope: `write:package`
|
||||
4. Copy the generated token
|
||||
5. Go to Repository Settings → Secrets → Add Secret
|
||||
6. Name: `REGISTRY_TOKEN`
|
||||
7. Value: Paste the token
|
||||
|
||||
### Registry Authentication
|
||||
|
||||
To pull images from the Gitea registry:
|
||||
|
||||
```bash
|
||||
# Login to registry
|
||||
docker login dev.pivoine.art
|
||||
|
||||
# Pull image
|
||||
docker pull dev.pivoine.art/valknar/supervisor-ui:latest
|
||||
```
|
||||
|
||||
### Image Tags
|
||||
|
||||
After pushing to main, the following tags are available:
|
||||
|
||||
```bash
|
||||
# Latest stable
|
||||
dev.pivoine.art/valknar/supervisor-ui:latest
|
||||
|
||||
# Specific commit
|
||||
dev.pivoine.art/valknar/supervisor-ui:main-abc1234
|
||||
|
||||
# Version tags (from git tags)
|
||||
dev.pivoine.art/valknar/supervisor-ui:v1.0.0
|
||||
dev.pivoine.art/valknar/supervisor-ui:1.0
|
||||
dev.pivoine.art/valknar/supervisor-ui:1
|
||||
|
||||
# Development branch
|
||||
dev.pivoine.art/valknar/supervisor-ui:develop
|
||||
```
|
||||
|
||||
## Production Deployment Scenarios
|
||||
|
||||
### Scenario 1: Supervisor on Same Host
|
||||
|
||||
Deploy UI on the same host as Supervisor:
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name supervisor-ui \
|
||||
--network host \
|
||||
-e SUPERVISOR_HOST=localhost \
|
||||
-e SUPERVISOR_PORT=9001 \
|
||||
dev.pivoine.art/valknar/supervisor-ui:latest
|
||||
```
|
||||
|
||||
Using `--network host` allows the container to access localhost services.
|
||||
|
||||
### Scenario 2: Supervisor on Different Host
|
||||
|
||||
Deploy UI on a different host:
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name supervisor-ui \
|
||||
-p 3000:3000 \
|
||||
-e SUPERVISOR_HOST=192.168.1.100 \
|
||||
-e SUPERVISOR_PORT=9001 \
|
||||
dev.pivoine.art/valknar/supervisor-ui:latest
|
||||
```
|
||||
|
||||
Ensure the Supervisor inet_http_server is accessible from the UI host (not bound to 127.0.0.1).
|
||||
|
||||
### Scenario 3: Multiple Supervisor Instances (Future)
|
||||
|
||||
While the current version connects to a single instance, you can run multiple UI containers:
|
||||
|
||||
```bash
|
||||
# Production Supervisor UI
|
||||
docker run -d \
|
||||
--name supervisor-ui-prod \
|
||||
-p 3000:3000 \
|
||||
-e SUPERVISOR_HOST=prod.supervisor.local \
|
||||
dev.pivoine.art/valknar/supervisor-ui:latest
|
||||
|
||||
# Staging Supervisor UI
|
||||
docker run -d \
|
||||
--name supervisor-ui-staging \
|
||||
-p 3001:3000 \
|
||||
-e SUPERVISOR_HOST=staging.supervisor.local \
|
||||
dev.pivoine.art/valknar/supervisor-ui:latest
|
||||
```
|
||||
|
||||
### Scenario 4: Behind Reverse Proxy (nginx/Traefik)
|
||||
|
||||
Example nginx configuration:
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name supervisor.example.com;
|
||||
|
||||
location / {
|
||||
proxy_pass http://localhost:3000;
|
||||
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;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Example Traefik docker-compose labels:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
supervisor-ui:
|
||||
image: dev.pivoine.art/valknar/supervisor-ui:latest
|
||||
environment:
|
||||
- SUPERVISOR_HOST=supervisor.local
|
||||
- SUPERVISOR_PORT=9001
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.supervisor-ui.rule=Host(`supervisor.example.com`)"
|
||||
- "traefik.http.routers.supervisor-ui.entrypoints=websecure"
|
||||
- "traefik.http.routers.supervisor-ui.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.services.supervisor-ui.loadbalancer.server.port=3000"
|
||||
```
|
||||
|
||||
### Scenario 5: Docker Compose Stack
|
||||
|
||||
Complete production-ready stack with reverse proxy:
|
||||
|
||||
```yaml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
supervisor-ui:
|
||||
image: dev.pivoine.art/valknar/supervisor-ui:latest
|
||||
container_name: supervisor-ui
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- SUPERVISOR_HOST=${SUPERVISOR_HOST}
|
||||
- SUPERVISOR_PORT=${SUPERVISOR_PORT}
|
||||
- SUPERVISOR_USERNAME=${SUPERVISOR_USERNAME}
|
||||
- SUPERVISOR_PASSWORD=${SUPERVISOR_PASSWORD}
|
||||
networks:
|
||||
- supervisor-network
|
||||
healthcheck:
|
||||
test: ["CMD", "node", "-e", "require('http').get('http://localhost:3000/api/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 5s
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.supervisor-ui.rule=Host(`supervisor.yourdomain.com`)"
|
||||
- "traefik.http.services.supervisor-ui.loadbalancer.server.port=3000"
|
||||
|
||||
networks:
|
||||
supervisor-network:
|
||||
external: true
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### 1. Credentials Management
|
||||
|
||||
**Bad** (credentials in image):
|
||||
```bash
|
||||
docker build --build-arg SUPERVISOR_PASSWORD=secret .
|
||||
```
|
||||
|
||||
**Good** (credentials at runtime):
|
||||
```bash
|
||||
docker run -e SUPERVISOR_PASSWORD=secret ...
|
||||
```
|
||||
|
||||
**Better** (use secrets management):
|
||||
```bash
|
||||
# Docker Swarm secrets
|
||||
echo "secret_password" | docker secret create supervisor_password -
|
||||
```
|
||||
|
||||
### 2. Network Isolation
|
||||
|
||||
Use Docker networks to isolate services:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
supervisor-ui:
|
||||
networks:
|
||||
- internal # Only accessible from other containers
|
||||
|
||||
nginx:
|
||||
networks:
|
||||
- internal
|
||||
- public # Exposed to internet
|
||||
```
|
||||
|
||||
### 3. Read-only Filesystem
|
||||
|
||||
For enhanced security, run with read-only root filesystem:
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--read-only \
|
||||
--tmpfs /tmp \
|
||||
-e SUPERVISOR_HOST=supervisor.local \
|
||||
dev.pivoine.art/valknar/supervisor-ui:latest
|
||||
```
|
||||
|
||||
### 4. Resource Limits
|
||||
|
||||
Prevent resource exhaustion:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
supervisor-ui:
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: '0.5'
|
||||
memory: 256M
|
||||
reservations:
|
||||
cpus: '0.25'
|
||||
memory: 128M
|
||||
```
|
||||
|
||||
## Monitoring & Health Checks
|
||||
|
||||
### Health Check Endpoint
|
||||
|
||||
The application provides a health check at `/api/health`:
|
||||
|
||||
```bash
|
||||
curl http://localhost:3000/api/health
|
||||
# {"status":"healthy","timestamp":"2025-11-23T17:00:00.000Z"}
|
||||
```
|
||||
|
||||
### Docker Health Check
|
||||
|
||||
Built-in health check monitors the API:
|
||||
|
||||
```bash
|
||||
docker inspect supervisor-ui --format='{{.State.Health.Status}}'
|
||||
```
|
||||
|
||||
### Integration with Monitoring Tools
|
||||
|
||||
#### Prometheus
|
||||
|
||||
Add health check as a target in `prometheus.yml`:
|
||||
|
||||
```yaml
|
||||
scrape_configs:
|
||||
- job_name: 'supervisor-ui'
|
||||
metrics_path: '/api/health'
|
||||
static_configs:
|
||||
- targets: ['supervisor-ui:3000']
|
||||
```
|
||||
|
||||
#### Uptime Kuma
|
||||
|
||||
Add HTTP(s) monitor:
|
||||
- Monitor Type: HTTP(s)
|
||||
- URL: `http://supervisor-ui:3000/api/health`
|
||||
- Interval: 60 seconds
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Container won't start
|
||||
|
||||
Check logs:
|
||||
```bash
|
||||
docker logs supervisor-ui
|
||||
```
|
||||
|
||||
Common issues:
|
||||
- Port 3000 already in use
|
||||
- Invalid environment variables
|
||||
- Network connectivity issues
|
||||
|
||||
### Can't connect to Supervisor
|
||||
|
||||
1. Check if Supervisor is accessible:
|
||||
```bash
|
||||
# From host
|
||||
curl http://supervisor-host:9001/RPC2
|
||||
|
||||
# From container
|
||||
docker exec supervisor-ui curl http://supervisor-host:9001/RPC2
|
||||
```
|
||||
|
||||
2. Verify Supervisor configuration in `/etc/supervisor/supervisord.conf`:
|
||||
```ini
|
||||
[inet_http_server]
|
||||
port = *:9001 ; Listen on all interfaces, not just 127.0.0.1
|
||||
```
|
||||
|
||||
3. Check environment variables:
|
||||
```bash
|
||||
docker exec supervisor-ui env | grep SUPERVISOR
|
||||
```
|
||||
|
||||
### Authentication failures
|
||||
|
||||
Verify credentials match your Supervisor configuration:
|
||||
|
||||
```bash
|
||||
# Test with curl
|
||||
curl -u username:password http://supervisor-host:9001/RPC2
|
||||
```
|
||||
|
||||
## Rollback Strategy
|
||||
|
||||
### Quick Rollback to Previous Version
|
||||
|
||||
```bash
|
||||
# Stop current version
|
||||
docker stop supervisor-ui
|
||||
docker rm supervisor-ui
|
||||
|
||||
# Run previous version
|
||||
docker run -d \
|
||||
--name supervisor-ui \
|
||||
-p 3000:3000 \
|
||||
--env-file .env \
|
||||
dev.pivoine.art/valknar/supervisor-ui:v1.0.0 # Specific version
|
||||
```
|
||||
|
||||
### Using Docker Compose
|
||||
|
||||
```bash
|
||||
# Edit docker-compose.yml to use previous tag
|
||||
# Then:
|
||||
docker-compose up -d --force-recreate
|
||||
```
|
||||
|
||||
## Backup & Recovery
|
||||
|
||||
### No Persistent Data
|
||||
|
||||
The Supervisor UI is stateless and doesn't store any data. All configuration is in environment variables.
|
||||
|
||||
To "backup" your deployment:
|
||||
1. Save your `.env` file or docker-compose.yml
|
||||
2. Document your Supervisor connection details
|
||||
|
||||
To "restore":
|
||||
1. Pull the image
|
||||
2. Apply your saved environment configuration
|
||||
3. Start the container
|
||||
|
||||
## Performance Tuning
|
||||
|
||||
### Node.js Optimization
|
||||
|
||||
For better performance in production:
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
-e NODE_OPTIONS="--max-old-space-size=256" \
|
||||
-e SUPERVISOR_HOST=supervisor.local \
|
||||
dev.pivoine.art/valknar/supervisor-ui:latest
|
||||
```
|
||||
|
||||
### Caching Strategies
|
||||
|
||||
The application uses React Query with intelligent caching:
|
||||
- System info: 5 second stale time
|
||||
- Processes: 3 second stale time
|
||||
- Logs: 2 second stale time
|
||||
|
||||
These are optimized for real-time monitoring while minimizing API calls.
|
||||
|
||||
## Scaling Considerations
|
||||
|
||||
### Horizontal Scaling
|
||||
|
||||
The application is stateless and can be scaled horizontally:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
supervisor-ui:
|
||||
image: dev.pivoine.art/valknar/supervisor-ui:latest
|
||||
deploy:
|
||||
replicas: 3
|
||||
update_config:
|
||||
parallelism: 1
|
||||
delay: 10s
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
```
|
||||
|
||||
### Load Balancing
|
||||
|
||||
Use nginx or Traefik to load balance across replicas:
|
||||
|
||||
```nginx
|
||||
upstream supervisor_ui {
|
||||
server supervisor-ui-1:3000;
|
||||
server supervisor-ui-2:3000;
|
||||
server supervisor-ui-3:3000;
|
||||
}
|
||||
|
||||
server {
|
||||
location / {
|
||||
proxy_pass http://supervisor_ui;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: November 23, 2025
|
||||
**Version**: 0.1.0
|
||||
Reference in New Issue
Block a user