Files
fn_registry/bash/functions/pipelines/dockerize_app.md
T
egutierrez 750b7abcd5 chore: auto-commit (97 archivos)
- .claude/CLAUDE.md
- .claude/agents/fn-recopilador/SKILL.md
- .claude/rules/INDEX.md
- .claude/rules/cpp_apps.md
- bash/functions/infra/build_cpp_windows.sh
- cpp/CMakeLists.txt
- cpp/PATTERNS.md
- cpp/framework/app_base.cpp
- cpp/framework/app_base.h
- dev/issues/README.md
- ...

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 18:11:24 +02:00

155 lines
7.5 KiB
Markdown

---
name: dockerize_app
kind: pipeline
lang: bash
domain: pipelines
version: "1.0.0"
purity: impure
signature: "dockerize_app(app_name: string, [--domain DOMAIN], [--port PORT], [--ssh-host HOST], [--remote-dir DIR], [--basic-auth USER:PASS], [--no-auth], [--no-gzip], [--env KEY=VAL]..., [--volume NAME], [--build-cmd CMD], [--standalone], [--dry-run]) -> json"
description: "Empaqueta una app Go del registry para deploy a VPS organic-machine via Docker + Traefik + Coolify. Genera Dockerfile multi-stage, docker-compose.yml, traefik-dynamic.yml con basicAuth opcional y gzip, sube via rsync al VPS y arranca el stack remoto. Replica el patron de apps/registry_api/."
tags: ["docker", "traefik", "coolify", "deploy", "pipeline", "launcher"]
uses_functions:
- generate_dockerfile_go_infra
- bcrypt_htpasswd_go_infra
- generate_compose_traefik_go_infra
- generate_traefik_dynamic_go_infra
- rsync_deploy_bash_infra
- docker_compose_remote_deploy_bash_infra
- health_check_http_go_infra
- gitea_create_repo_bash_infra
- gitea_push_directory_bash_infra
uses_types:
- ComposeTraefikConfig_go_infra
- TraefikDynamicConfig_go_infra
returns: []
returns_optional: false
error_type: "error_go_core"
imports: []
params:
- name: app_name
desc: "Nombre o ID parcial de la app en registry.db (ej: kanban, deploy_server). Se busca con LIKE '<app_name>%' OR name='<app_name>'."
- name: domain
desc: "Dominio publico completo para el router Traefik (ej: kanban.organic-machine.com). Obligatorio."
- name: port
desc: "Puerto interno del contenedor Docker (default: 8080). Debe coincidir con el puerto en que la app escucha."
- name: ssh-host
desc: "Alias o IP del host SSH destino (default: organic-machine.com). Debe estar en ~/.ssh/config o ser accesible con key auth."
- name: remote-dir
desc: "Ruta absoluta en el VPS donde se desplegara la app (default: /home/ubuntu/coolify-apps/<app_name>). En modo rsync apunta al subdir de la app dentro del build root."
- name: basic-auth
desc: "Credenciales para basicAuth de Traefik en formato USER:PASS. Obligatorio si auth esta ON (defecto). Se hashea con bcrypt via htpasswd o python3+bcrypt."
- name: no-auth
desc: "Flag para deshabilitar basicAuth. Por defecto auth esta habilitado; se requiere --basic-auth USER:PASS si no se pasa --no-auth."
- name: no-gzip
desc: "Flag para deshabilitar el middleware gzip de Traefik. Por defecto gzip esta habilitado."
- name: env
desc: "Variable de entorno KEY=VAL a incluir en el .env y en la seccion environment del docker-compose.yml. Repetible para multiples vars."
- name: volume
desc: "Nombre de un Docker volume que se monta en /data dentro del contenedor. Se declara en la seccion volumes del compose."
- name: build-cmd
desc: "Comando de build personalizado (documentado para uso futuro; Phase 1 usa el Dockerfile multi-stage generado)."
- name: standalone
desc: "Modo standalone: crea repo Gitea dataforge/<app> y usa git clone/pull en el VPS en vez de rsync. Requiere GITEA_URL y credenciales Gitea configuradas."
- name: dry-run
desc: "Imprime los artefactos generados (Dockerfile, docker-compose.yml, traefik-dynamic.yml, .env) a stderr y retorna JSON con status=dry-run sin ejecutar ningun comando remoto ni escribir ficheros en la app."
output: "JSON a stdout: {status, app, domain, remote_dir, container_id, duration_seconds, auth_enabled, gzip_enabled, http_code, url}. status='ok' si el health check responde HTTP 200/401, 'failed' si hay timeout, 'dry-run' en modo dry-run."
tested: false
tests: []
test_file_path: ""
file_path: "bash/functions/pipelines/dockerize_app.sh"
---
## Ejemplo
```bash
# Deploy completo con basicAuth
cd /home/lucas/fn_registry
bash bash/functions/pipelines/dockerize_app.sh kanban \
--domain kanban.organic-machine.com \
--port 8421 \
--basic-auth lucas:supersecret \
--env KANBAN_DB=/data/kanban.db \
--volume kanban_data
# Salida esperada:
# {"status":"ok","app":"kanban","domain":"kanban.organic-machine.com","remote_dir":"...","container_id":"abc123","duration_seconds":45,"auth_enabled":true,"gzip_enabled":true,"http_code":"401","url":"https://kanban.organic-machine.com"}
# Deploy sin auth (app publica)
bash bash/functions/pipelines/dockerize_app.sh registry_api \
--domain registry.organic-machine.com \
--port 8080 \
--no-auth \
--env REGISTRY_API_TOKEN=mytoken
# Dry-run: ver YAMLs sin tocar nada
bash bash/functions/pipelines/dockerize_app.sh kanban \
--dry-run \
--domain kanban.organic-machine.com \
--port 8421 \
--basic-auth lucas:test123
# Standalone: repo Gitea + git clone en VPS
bash bash/functions/pipelines/dockerize_app.sh deploy_server \
--domain deploy.organic-machine.com \
--port 9090 \
--basic-auth lucas:secret \
--standalone
```
## Pasos internos
| Paso | Descripcion |
|------|-------------|
| 1 | Valida la app en registry.db (SQL sobre tabla apps) |
| 2 | Valida conectividad SSH (BatchMode, ConnectTimeout=5) |
| 3 | Genera hash bcrypt via htpasswd o python3+bcrypt |
| 4 | Genera Dockerfile multi-stage Go (heredoc, patron generate_dockerfile_go_infra) |
| 5 | Genera docker-compose.yml con Traefik labels y red coolify |
| 6 | Genera traefik-dynamic.yml con routers HTTP/HTTPS, basicAuth, gzip, certResolver letsencrypt |
| 7 | Genera/actualiza .env con merge no destructivo |
| 8 | Rsync del repo completo al VPS (o git clone en standalone) |
| 9 | Crea directorio remoto de deploy |
| 10 | Sube traefik-dynamic.yml a /data/coolify/proxy/dynamic/<name>.yml via SSH+sudo tee |
| 11 | Sube docker-compose.yml, Dockerfile y .env al remote_dir via scp |
| 12 | Crea red Docker coolify si no existe; `docker compose up -d --build` remoto |
| 13 | Health check: 10 intentos, 3s intervalo, acepta HTTP 200 y 401 |
## Decision de implementacion
Los YAMLs se generan con heredocs bash inline, replicando la logica de
`generate_dockerfile_go_infra`, `generate_compose_traefik_go_infra` y
`generate_traefik_dynamic_go_infra`. Esta decision evita crear un nuevo
binario `cmd/dockerize_helpers/` y mantiene el pipeline completamente
self-contained, siguiendo el patron de `setup_registry_api_bash_infra`.
Las funciones Go quedan referenciadas en `uses_functions` como fuente de
verdad documental del patron que replica.
## Requisitos en el host local
- `ssh` y `rsync` instalados
- `htpasswd` (apache2-utils) o `python3` + modulo `bcrypt` para generar hash
- Acceso SSH sin password al host destino (key en ~/.ssh/config)
- `sqlite3` CLI para leer registry.db
## Requisitos en el VPS
- Docker + docker compose (v2)
- Coolify con Traefik corriendo y `/data/coolify/proxy/dynamic/` accesible via sudo
- Red Docker `coolify` (se crea automaticamente si no existe)
- Usuario SSH con sudo sin password para: `mkdir`, `tee` en `/data/coolify/proxy/dynamic/`
## Codigos de salida
| Codigo | Significado |
|--------|-------------|
| 0 | Exito: stack activo y health check OK |
| 1 | Error: app no encontrada, SSH inavalcable, fallo de build, health check timeout |
## Notas
- Phase 1 soporta exclusivamente apps `lang: go`. Soporte para Python y Bash en fases futuras.
- El Dockerfile se genera solo si no existe en el directorio de la app. Si ya existe (con tweaks manuales), se preserva y se avisa por stderr.
- El build context del Dockerfile es `../../` relativo a `apps/<app>/` para que el multi-stage copie el `go.mod` del root del registry y compile correctamente.
- El nombre del router Traefik reemplaza `_` por `-` (ej: `registry_api``registry-api`).
- En modo `--standalone`, se requieren las variables `GITEA_URL`, `GITEA_TOKEN` (o `GITEA_USER`/`GITEA_PASS`) configuradas para `gitea_create_repo` y `gitea_push_directory`.