--- 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 '%' OR 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/). 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/ 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/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/.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//` 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`.