Files
scopone/.github/skills/deploy-app/README.md
2026-04-10 22:35:01 +02:00

602 lines
18 KiB
Markdown

# Deploy Application Skill
Automated deployment of Dockerized applications to remote servers.
## Features
**Automated Docker builds and registry pushes** to Gitea
**SSH-based remote deployment** with compose orchestration
**Automatic reverse proxy** configuration with Nginx Proxy Manager
**Subdomain routing** - Apps served as `{app-name}.{root-domain}`
**SSL certificate** generation via Let's Encrypt
**Smart .env handling** - Auto-generates secrets, infers service URLs
**Network topology** management (proxy, ollama)
**No host port publishing** - Webapps are reached through Docker network + Nginx Proxy Manager
**Organized data storage** - All volumes under `./data/`
**Health monitoring** and log inspection
**Rollback guidance** on failures
**Update-aware deployments** - Detects existing stack/proxy and performs safe in-place updates
## Quick Start
### 1. Run Setup Wizard
```bash
cd .github/skills/deploy-app
chmod +x setup-wizard.sh
./setup-wizard.sh
```
Follow the prompts to generate your deployment configuration.
### 2. Deploy Your App
```
deploy production
```
That's it! The agent will handle both first deploys and updates.
If the remote stack directory or proxy host already exists, deployment is treated as an **update** (with idempotent proxy handling and additive env/volume changes).
## Files
- **SKILL.md** - Complete skill documentation (for the agent)
- **setup-wizard.sh** - Interactive configuration generator
- **README.md** - This file (user documentation)
## What Gets Deployed
```
Remote Server: /srv/docker/stacks/my-app/
├── compose.yaml # Generated Docker Compose file
├── .env # Environment variables (if detected)
└── data/ # Persistent application data
├── <app-data> # Main application data
├── uploads/ # If app needs uploads (auto-detected)
├── cache/ # If app needs cache (auto-detected)
└── logs/ # If app needs logs (auto-detected)
```
All volumes are organized under the `data/` directory for clean separation.
## Supported Application Types
- **webapp** - Web applications with reverse proxy and SSL
- **service** - Backend services without public access
- **worker** - Background workers and queue processors
## Requirements
**Local:**
- Docker installed
- Access to workspace with Dockerfile
- Environment variable `DEPLOY_CONFIG_{TARGET}` configured
**Remote Server:**
- Docker and Docker Compose installed
- SSH access configured
- Docker networks created (`proxy`, optionally `ollama`)
**Services:**
- Gitea instance with container registry enabled
- Nginx Proxy Manager (for webapps)
## Configuration
See configuration examples below for different deployment scenarios.
### Production Server
```bash
export DEPLOY_CONFIG_PRODUCTION='{
"gitea_registry_url": "gitea.mycompany.com",
"gitea_username": "deploy-bot",
"gitea_token": "a1b2c3d4e5f6g7h8i9j0",
"gitea_namespace": "production-apps",
"ssh_host": "prod-server",
"stacks_dir": "/srv/docker/stacks",
"domain": "sal.giize.com",
"npm_url": "https://proxy.mycompany.com",
"npm_username": "admin@mycompany.com",
"npm_password": "npm-admin-password"
}'
# All app-specific settings (name, port, env vars, volumes, type)
# are auto-detected from your codebase!
# App will be served at: {app-name}.sal.giize.com
```
### Staging Server
```bash
export DEPLOY_CONFIG_STAGING='{
"gitea_registry_url": "gitea.mycompany.com",
"gitea_username": "deploy-bot",
"gitea_token": "a1b2c3d4e5f6g7h8i9j0",
"gitea_namespace": "staging-apps",
"ssh_host": "staging-server",
"stacks_dir": "/home/deploy/stacks",
"domain": "staging.sal.giize.com",
"npm_url": "https://proxy-staging.mycompany.com",
"npm_username": "admin@mycompany.com",
"npm_password": "npm-admin-password"
}'
```
### AI Service with LLM
```bash
export DEPLOY_CONFIG_AI_SERVICE='{
"gitea_registry_url": "gitea.mycompany.com",
"gitea_username": "deploy-bot",
"gitea_token": "a1b2c3d4e5f6g7h8i9j0",
"gitea_namespace": "ai-services",
"ssh_host": "ai-server",
"stacks_dir": "/srv/docker/stacks",
"domain": "ai.sal.giize.com",
"npm_url": "https://proxy.mycompany.com",
"npm_username": "admin@mycompany.com",
"npm_password": "npm-admin-password",
"proxy_network": "proxy",
"ollama_network": "ollama",
"ollama_container_name": "ollama"
}'
# Network names are customizable
# App will auto-connect to Ollama at: http://ollama:11434
# (or your custom ollama_container_name:11434)
```
### Background Worker (No Web Interface)
For services without a web interface, omit the NPM fields:
```bash
export DEPLOY_CONFIG_WORKER='{
"gitea_registry_url": "gitea.mycompany.com",
"gitea_username": "deploy-bot",
"gitea_token": "a1b2c3d4e5f6g7h8i9j0",
"gitea_namespace": "workers",
"ssh_host": "worker-server",
"stacks_dir": "/srv/docker/stacks"
}'
```
### Configuration Fields
**Required for All Deployments:**
- `gitea_registry_url` - Your Gitea instance domain (without https://)
- `gitea_username` - Gitea username
- `gitea_token` - Access token with package permissions
- `gitea_namespace` - Organization or username
- `ssh_host` - SSH alias from ~/.ssh/config
- `stacks_dir` - Directory on remote server for stacks
**Required for Webapps:**
- `domain` - Root domain (e.g., `sal.giize.com`). App will be served at `{app-name}.{domain}`
- `npm_url` - Nginx Proxy Manager URL
- `npm_username` - NPM admin email
- `npm_password` - NPM admin password
**Optional Network Configuration:**
- `proxy_network` - Docker network name for reverse proxy (default: `proxy`)
- `ollama_network` - Docker network name for Ollama LLM (default: `ollama`)
- `ollama_container_name` - Ollama container name for URL generation (default: `ollama`)
- Apps requiring LLM will auto-set `OLLAMA_BASE_URL=http://{ollama_container_name}:11434`
**Auto-Detected from Codebase:**
- **App name** - From package.json, Cargo.toml, pyproject.toml, go.mod, or directory name
- **Port** - From Dockerfile EXPOSE, .env PORT, code patterns, or defaults to 3000
- Used as internal container port for proxy upstream (not published on host by default)
- **App type** - webapp/service/worker based on dependencies and keywords
- **Environment variables** - Smart .env processing:
- 🔐 Auto-generates secrets (JWT_SECRET, APP_KEY, etc.) using secure random values
- 🔌 Infers service URLs (DATABASE_URL, REDIS_URL) from dependencies
- 📋 Copies simple values (NODE_ENV, LOG_LEVEL) as-is
- ❓ Asks for external API keys and credentials
- **Volume mounts** - From README, Dockerfile VOLUME, common directories (bound under ./data/)
- **LLM requirements** - From dependencies (openai, ollama, llama)
All detected settings are confirmed with you before deployment.
**Volume Binding:** All volumes are automatically bound under `./data/` on the host.
Example: If app needs uploads, it creates `./data/uploads:/app/uploads`
**Domain Handling:** The `domain` field is the root domain. Your app is served as a subdomain.
Example: `domain: "sal.giize.com"` + app name `my-api` → URL: `my-api.sal.giize.com`
**DNS Requirements:** Ensure you have a wildcard DNS record or individual A/CNAME records:
- Wildcard: `*.sal.giize.com``your-server-ip`
- Individual: `my-api.sal.giize.com``your-server-ip`
## Deployment Flow
1. **Parse target** - Extract deployment target name
2. **Auto-detect app** - Scan codebase for app name, port, type, env vars, volumes, LLM usage
3. **Process .env** - Generate secrets, infer service URLs, identify user inputs
4. **Load config** - Get server/credential configuration
5. **Detect mode** - Classify deployment as `new` or `update` using existing remote stack/proxy state
6. **Confirm detection** - Show detected config and mode, ask for approval
7. **Confirm environment** - Review .env processing and provide any needed values
8. **Validate** - Check Dockerfile exists
9. **Build** - Test build locally
10. **Push** - Tag and push to Gitea registry
11. **Transfer** - SSH to remote server
12. **Setup** - Create/update directories, deploy merged `.env` and compose files
13. **Deploy** - `docker compose down`, `docker compose pull`, `docker compose up -d`
14. **Configure** - Create or update reverse proxy (webapps)
- Upstream target uses Docker service + internal port (for example `my-app:3000`)
15. **Verify** - Inspect logs and test application availability
## Key Features Explained
### Subdomain Routing
Apps are automatically served as subdomains of your root domain:
- Config: `"domain": "sal.giize.com"`
- App name: `my-api` (auto-detected)
- **Result:** App accessible at `my-api.sal.giize.com`
**DNS Setup:** Configure wildcard DNS: `*.sal.giize.com` → your server IP
### Volume Organization
All persistent data is organized under `./data/`:
```
data/
├── <app-data>/ # Main app data (always created)
├── uploads/ # Auto-detected from codebase
├── cache/ # Auto-detected from codebase
└── logs/ # Auto-detected from codebase
```
Docker compose mounts: `./data/uploads:/app/uploads`
### Auto-Detection
The skill scans your codebase to detect:
- App name from package.json, Cargo.toml, pyproject.toml, go.mod, or directory
- Container port from Dockerfile EXPOSE, .env, or code patterns
- App type (webapp/service/worker) from dependencies
- Environment variables from .env files
- Volume requirements from README, Dockerfile, common patterns
- LLM dependencies (openai, ollama)
All detected settings shown to you for confirmation before deployment.
### Smart .env Handling
When a `.env`, `.env.example`, or `.env.template` file is detected, the skill intelligently processes it:
**🔐 Auto-Generated Secrets**
Variables like `JWT_SECRET`, `APP_KEY`, `SESSION_SECRET` are automatically generated using secure random values:
```bash
JWT_SECRET=a1b2c3d4e5f6... # 32-byte hex string
```
**🔌 Inferred Service URLs**
Database and service connections are constructed from container names based on detected dependencies:
```bash
DATABASE_URL=postgresql://my-app-db:5432/my-app
REDIS_URL=redis://my-app-redis:6379
MONGODB_URI=mongodb://my-app-mongo:27017/my-app
```
**📋 Copied Values**
Simple configuration values are preserved:
```bash
NODE_ENV=production
LOG_LEVEL=info
TZ=UTC
```
**❓ User Input**
External API keys and credentials are identified and you're prompted for values:
```bash
STRIPE_SECRET_KEY=sk_live_... # You'll be asked to provide this
SENDGRID_API_KEY=SG... # You'll be asked to provide this
```
**Confirmation**
Before deployment, you'll see:
- What was generated (with secure random values)
- What was inferred (service URLs)
- What was copied (simple configs)
- What needs your input (API keys)
You can proceed as-is or provide specific values. The complete `.env` file is deployed to the server with secure permissions (chmod 600).
### Update-Safe Drift Handling
When deployment mode is `update`, the skill applies additive and non-destructive changes:
- **Environment merge:** existing remote `.env` keys are preserved; newly required keys are appended.
- **Required secrets:** unresolved required placeholders still block deployment until provided.
- **Volume drift:** new host directories are created as needed; existing volume paths are kept intact.
- **Compose stability:** service naming remains stable for idempotent updates.
This minimizes downtime and avoids accidental configuration loss during redeployments.
## Quick Setup Guide
### 1. Create Gitea Token
```bash
# Login to Gitea
# Navigate to Settings → Applications → Generate New Token
# Select scopes: write:package, read:package
# Copy the generated token
```
### 2. Configure SSH
```bash
# Edit ~/.ssh/config
cat >> ~/.ssh/config << 'EOF'
Host prod-server
HostName 192.168.1.100
User deploy
IdentityFile ~/.ssh/deploy_key
StrictHostKeyChecking accept-new
EOF
# Test connection
ssh prod-server "echo 'Connection successful'"
```
### 3. Prepare Remote Server
```bash
# SSH to server
ssh prod-server
# Install Docker
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
# Create networks (run once per server)
# Use your configured network names (defaults shown below)
docker network create proxy # or your proxy_network value
docker network create ollama # or your ollama_network value (for LLM apps)
# Create stacks directory
sudo mkdir -p /srv/docker/stacks
sudo chown $USER:$USER /srv/docker/stacks
```
### 4. Set Environment Variable
**Linux/macOS** (add to ~/.bashrc or ~/.zshrc):
```bash
export DEPLOY_CONFIG_PRODUCTION='{ ... your JSON config ... }'
```
**Windows PowerShell**:
```powershell
$config = @'
{ ... your JSON config ... }
'@
[System.Environment]::SetEnvironmentVariable('DEPLOY_CONFIG_PRODUCTION', $config, 'User')
```
### 5. Deploy
Just tell the agent:
```
deploy production
```
## Security
- Credentials stored in environment variables only
- SSH key-based authentication
- Secrets never logged to output
- HTTPS with Let's Encrypt certificates
- Docker registry authentication
## Troubleshooting
### Quick Diagnostics
```bash
# Check environment variable
echo $DEPLOY_CONFIG_PRODUCTION
# Test SSH
ssh prod-server "docker ps"
# Test Gitea registry
docker login gitea.example.com -u username
# Check remote deployment
ssh prod-server "cd /srv/docker/stacks/my-app && docker compose logs"
```
### Common Issues
**"DEPLOY_CONFIG_{TARGET} not found"**
- Make sure you've exported the environment variable
- Restart your terminal/VS Code after setting it
- Check spelling and capitalization
**"Cannot connect to {ssh_host}"**
- Verify SSH config: `cat ~/.ssh/config`
- Test connection: `ssh {ssh_host} "echo test"`
- Check SSH key permissions: `chmod 600 ~/.ssh/deploy_key`
**"Authentication failed" (Gitea)**
- Regenerate token with correct permissions
- Ensure token hasn't expired
- Verify username is correct
**"Docker login failed"**
- Check gitea_registry_url doesn't have protocol (no https://)
- Verify token has package write permission
- Try manual login: `echo "TOKEN" | docker login gitea.example.com -u user --password-stdin`
**Container fails to start**
- Check logs: `ssh server "cd /srv/docker/stacks/app && docker compose logs"`
- Verify environment variables are correct
- Check port conflicts on server
- Ensure image pulled successfully
**Environment variables not working**
- Check .env file exists: `ssh server "cat /srv/docker/stacks/app/.env"`
- Verify .env file permissions: `ssh server "ls -la /srv/docker/stacks/app/.env"` (should be 600)
- Ensure compose.yaml has `env_file: [.env]` directive
- Test variable loading: `ssh server "cd /srv/docker/stacks/app && docker compose config"` (shows resolved env vars)
- Restart containers after .env changes: `ssh server "cd /srv/docker/stacks/app && docker compose restart"`
**Missing or incorrect service URLs**
- Verify service containers are running in same network
- Check container names match .env URLs (e.g., `my-app-db` for DATABASE_URL)
- Test connectivity: `ssh server "docker exec my-app ping my-app-db"`
- Review auto-generated URLs in deployment confirmation step
**NPM SSL certificate fails**
- Verify domain DNS points to your server
- Check ports 80 and 443 are open
- Ensure domain is publicly accessible
- Check NPM can reach Let's Encrypt servers
## API Documentation
### Gitea Container Registry
Gitea uses an OCI-compliant container registry. No special API calls needed - just standard Docker commands:
```bash
docker login {gitea_url}
docker tag image:tag {gitea_url}/{namespace}/{name}:tag
docker push {gitea_url}/{namespace}/{name}:tag
```
Images are automatically visible in Gitea under Packages.
### Nginx Proxy Manager API
Main endpoints used:
- `POST /api/tokens` - Authentication
- `POST /api/nginx/proxy-hosts` - Create reverse proxy
- `POST /api/nginx/certificates` - Request SSL cert
- `PUT /api/nginx/proxy-hosts/{id}` - Update with SSL
## Examples
### Deploy to Production
```
deploy production
```
If `production` already exists remotely, this runs as an update rollout.
### Deploy to Staging
```
deploy staging
```
If the stack already exists, the skill updates it in place.
### Deploy AI Service with LLM
```
deploy ai-service
```
For existing deployments, proxy and stack are updated idempotently.
## Advanced Usage
### Modifying Environment Variables After Deployment
If you need to update environment variables after deployment:
**Option 1: Edit .env on server**
```bash
# SSH to server and edit
ssh prod-server
cd /srv/docker/stacks/my-app
nano .env
# Restart containers to apply changes
docker compose restart
```
**Option 2: Redeploy with updated .env**
Update your local `.env` file and redeploy - the skill will process it again in update mode and merge new required keys without deleting existing ones.
### Adding Service Containers
If your app needs a database or other services, create them in the same stack:
```bash
ssh prod-server
cd /srv/docker/stacks/my-app
nano compose.yaml
```
Add service definitions:
```yaml
services:
my-app:
# ... existing config
depends_on:
- db
- redis
db:
image: postgres:16
container_name: my-app-db
environment:
POSTGRES_DB: my-app
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- ./data/postgres:/var/lib/postgresql/data
redis:
image: redis:7
container_name: my-app-redis
volumes:
- ./data/redis:/data
```
The DATABASE_URL and REDIS_URL will already be correctly set by the auto-detection!
### Additional Volumes
```json
{
"volumes": [
"./uploads:/app/uploads",
"./cache:/app/cache:ro"
]
}
```
### Multiple Targets
Create multiple configs:
```bash
export DEPLOY_CONFIG_PRODUCTION='{ ... }'
export DEPLOY_CONFIG_STAGING='{ ... }'
export DEPLOY_CONFIG_DEVELOPMENT='{ ... }'
```
Deploy to any target:
```
deploy development
deploy staging
deploy production
```
## License
Part of the Copilot Agents skill library.