177 lines
6.5 KiB
Markdown
177 lines
6.5 KiB
Markdown
---
|
|
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). |