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