---
name: deploy-app
description: Automated deployment of Dockerized applications to remote servers with Gitea registry, Docker Compose orchestration, and Nginx Proxy Manager integration.
---

# Deploy Application

Lean-mode deployment contract for token-efficient execution. Use this file for runtime behavior. For deep examples and full walkthroughs, reference [README.md](./README.md).

## Prerequisites

- `DEPLOY_CONFIG_{TARGET}` env var exists and is valid JSON
- SSH host alias works from `~/.ssh/config`
- `Dockerfile` exists in workspace root
- Docker available locally and on target server

## Deployment Steps

### 1. Parse target

Extract `{TARGET}` from user request (for example: `deploy production` → `PRODUCTION`).

### 2. Detect app metadata (minimal set)

Detect and store:
- `{app_name}`: from `package.json` / `Cargo.toml` / `pyproject.toml` / `go.mod` / directory fallback
- `{port}`: from `EXPOSE`, `.env PORT`, or framework patterns; fallback `3000` (container internal port)
- `{app_type}`: `webapp`, `service`, or `worker`
- `{requires_llm}`: true if `openai|ollama|llama` detected
- `{detected_volumes}`: bind all host paths under `./data/` (including secrets paths)

If `.env*` exists (excluding `.env.local`), process variables into:
- generated secrets (`*SECRET*`, `*KEY`, `*TOKEN`)
- inferred service URLs (`DATABASE_URL`, `REDIS_URL`, etc.)
- copied simple values (`NODE_ENV`, `PORT`, `LOG_LEVEL`, ...)
- user-required values (placeholders/external API keys)

### 3. Load and validate deploy config

Read `DEPLOY_CONFIG_{TARGET}` and validate required fields:
- `gitea_registry_url`, `gitea_username`, `gitea_token`, `gitea_namespace`
- `ssh_host`, `stacks_dir`

If missing/invalid: STOP with explicit error.

### 4. Confirm detected configuration

Use `ask_questions` with detected summary (app, port, type, env, volumes, URL if webapp, deploy mode).
If user declines, STOP and request adjustments.

If user-input env values are required, collect them before continuing.

### 5. Validate and build locally

Verify Dockerfile exists, then run:
- `docker build -t {app_name}:test-build .`

If build fails: STOP and report error output.

### 6. Push image to registry

1. Login with `--password-stdin`
2. Tag as `latest` and `{timestamp}` (`YYYYMMDD-HHMMSS`)
3. Push both tags

If auth/network/permission fails: STOP with targeted troubleshooting.

### 7. Detect deploy mode (new vs update)

On remote host and in NPM API (when configured), detect:
- stack path exists: `{stacks_dir}/{app_name}`
- compose file exists: `{stacks_dir}/{app_name}/compose.yaml`
- proxy host exists for `{app_name}.{domain}` (webapp only)

Classification:
- `new`: none of the above exist
- `update`: stack path or proxy host already exists

If only one side exists (for example proxy exists but stack does not, or inverse), continue in cautious update mode and include this mismatch in confirmation.

### 8. Prepare remote stack

1. Verify SSH connectivity
2. Create `{stacks_dir}/{app_name}/data` and required subdirectories
3. Process `.env` with update-safe merge strategy:
	- if update and remote `.env` exists, preserve existing keys/values
	- append newly required keys not present remotely
	- never delete existing keys automatically
	- block on unresolved required user-input secrets/placeholders
4. Apply secure permissions to remote `.env` (`chmod 600`)
5. Handle volume drift safely:
	- create newly required host directories
	- enforce all volume and secrets host paths as children of `./data/`
	- keep existing volume paths intact
	- never remove existing volume mounts automatically

### 9. Generate and upload compose.yaml

Compose requirements:
- image: `{gitea_registry_url}/{gitea_namespace}/{app_name}:latest`
- restart: `unless-stopped`
- all volumes and secrets host paths under `./data`
- do NOT publish ports on host (no `ports:` mapping by default)
- use external proxy network for `webapp`
- add ollama network and `OLLAMA_BASE_URL` when `{requires_llm}=true`

Update behavior for compose generation:
- keep service naming stable for idempotent updates
- preserve existing mounts/networks unless user explicitly confirms removal
- if update and env + volume definitions are unchanged, do NOT rewrite `compose.yaml`
- if update requires compose rewrite, fetch remote `compose.yaml` and use it as template baseline before applying required deltas

Upload compose file to `{stacks_dir}/{app_name}/compose.yaml`.

### 10. Deploy on server

On remote host:
1. `docker login` to registry
2. `docker compose down`
3. `docker compose pull`
4. `docker compose up -d`
4. Inspect logs (`--tail=50`) after short wait

If container exits or logs show startup failure: STOP and report logs.

### 11. Configure reverse proxy (webapp only)

If `{app_type}=webapp` and NPM config exists:
1. Authenticate to NPM API
2. Check if proxy host for `{app_name}.{domain}` already exists
3. Set upstream target to Docker service name + internal container port (`{app_name}:{port}`) on shared proxy network
4. If existing: update upstream target and SSL settings (idempotent update)
5. If not existing: create proxy host, request certificate, then enable SSL

If SSL creation fails: WARN and continue HTTP-only.

### 12. Verify deployment

- Webapp: `curl -I https://{app_name}.{domain}` and expect `2xx/3xx`
- Non-webapp: verify running container and health/log readiness

Return structured success summary (image, host, location, URL/container).

## Error Recovery

On failure:
1. Show concrete error
2. Provide immediate remediation steps
3. STOP (no implicit continuation)
4. Offer rollback option using ONLY non-destructive actions:
	- run `docker compose down` in the app stack directory
	- rollback changes in Nginx Proxy Manager (remove/disable created proxy host entries for this deploy)

## Forbidden Operations

Safety policy for this skill is strict:
- NEVER execute deletion commands on local or remote systems.
- NEVER run any `rm`-family or delete operations, including recursive deletes.
- NEVER use destructive cleanup commands that remove files, directories, volumes, or containers.

Allowed remediation is limited to:
- `docker compose down`
- rollback changes in Nginx Proxy Manager

If a remediation path would require deletion, STOP and report manual follow-up steps without executing destructive commands.

## Security

- Never print secrets in logs
- Use `--password-stdin` for all docker logins
- Quote shell variables
- Validate all required inputs before executing remote operations

## Notes

- Keep runtime behavior minimal and deterministic.
- For advanced examples, edge-case recipes, and setup wizard usage, see [README.md](./README.md).