Communitygithub.com

app-platform-publish

Publica un sitio web o aplicación en DigitalOcean App Platform y devuelve su URL pública. Úsalo cuando el usuario pida publicar, desplegar, subir, alojar o lanzar algo que hayas creado. Úsalo EN LUGAR de la skill `publish` / herramienta `apps`, que necesita Docker y no funciona aquí. Incluye subir el código a GitHub, crear la aplicación en App Platform, esperar el despliegue y solucionar fallos a partir de los logs de compilación.

¿Qué es app-platform-publish?

Esta skill permite publicar una aplicación web o sitio estático en DigitalOcean App Platform directamente desde un sandbox de agente, sin necesidad de Docker ni `doctl`. El flujo completo incluye: probar la aplicación localmente, prepararla para ser desplegable (escuchando en `$PORT`, sin disco persistente, con soporte para Postgres gestionado), proponer un plan al usuario y obtener su aprobación explícita, crear un repositorio público en GitHub bajo la cuenta compartida `jnaidu-do`, crear la aplicación en App Platform (como sitio estático o servicio), esperar a que esté activa y devolver la URL pública. También maneja fallos leyendo los logs y proponiendo correcciones, pero siempre pidiendo aprobación antes de cada cambio. Soporta aplicaciones Node, Python, HTML/CSS/JS plano o frameworks que compilan a estáticos (Vite, Astro) y aplicaciones con servidor. No requiere que el usuario tenga su propia cuenta de DigitalOcean; todo se hace con credenciales compartidas a través del broker de credenciales del núcleo.

Compatible con~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/jnaidu-do/qm-appplatform-skills/tree/main/app-platform-publish

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Publicar en App Platform

Flujo: compilar y probar en el sandbox → hacerlo desplegable → proponer el plan y obtener un sí → subir a GitHub → crear la aplicación en App Platform → esperar a que esté ACTIVA → comprobar la URL.

Nunca crees un repositorio, subas código o crees/actualices/elimines una aplicación antes de que el usuario haya aprobado el plan en el paso 2. Una solicitud como "publícalo" no es esa aprobación. Compilar y probar en el sandbox no necesita aprobación.

Nunca uses la acción publish de apps, docker ni doctl — nada de eso funciona en este ordenador. Todo pasa a través de dos credenciales compartidas, que invocas mediante el broker de credenciales del núcleo. Nunca ves los tokens.

Usa un único sandbox_id explícito para cada comando, y selecciona la credencial en execute.credentials para cualquier comando que llame al broker (digitalocean, github o github-git). El núcleo proporciona AGENT_CREDENTIAL_TOKEN solo a esos comandos. No uses rm -rf; crea un directorio nuevo con mktemp -d.

Qué tiene el ordenador

Node 24 + npm, Python 3.12 + pip, git, gh (no iniciado sesión; no lo uses), curl, gcc, make. No docker, doctl, jq, wget, unzip. Analiza JSON con node -e o python3 -c.

Credenciales (qué puede hacer cada una)

CredencialHostPermitido
digitaloceanapi.digitalocean.comGET/POST/PUT/DELETE en /v2/apps y /v2/regions
githubapi.github.comGET/POST/PATCH en /user y /repos/jnaidu-do
github-gitgithub.com (git push/clone)GET/POST en /jnaidu-do/

Todo se crea bajo la cuenta compartida de GitHub jnaidu-do (una credencial de todo el despliegue, no la del usuario). Dilo en el plan. Si el usuario quiere su propia cuenta, dile que aún no está configurada. Un 403 path_not_allowed / method_not_allowed significa que la solicitud está fuera de eso — no intentes variaciones; dile al usuario que no está permitido.

Ayudante del broker

Pega esto una vez por comando (el estado del shell no persiste). Imprime el estado HTTP en stderr y el cuerpo de la respuesta en stdout:

bk() { # bk <credencial> <MÉTODO> <url> [cuerpo-json]
  p=$(node -e 'const [c,m,u,b]=process.argv.slice(1);console.log(JSON.stringify({credential:c,method:m,url:u,headers:{accept:"application/json","content-type":"application/json"},...(b?{body:b}:{})}))' "$1" "$2" "$3" "${4:-}")
  curl -fsS -X POST "$AGENT_API_URL/v1/credentials/broker" -H "x-agent-capability: $AGENT_CREDENTIAL_TOKEN" \
    -H "content-type: application/json" -d "$p" |
  node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{const r=JSON.parse(s);console.error("HTTP "+r.status);console.log(r.body)})'
}

1. Hacerlo desplegable (verificar antes de subir)

La aplicación se ejecuta como un contenedor normal detrás de App Platform, no en este sandbox.

  • Escucha en $PORT, vinculado a 0.0.0.0 (App Platform establece PORT; por defecto 8080). Nunca uses otro puerto fijo.
  • HTML/CSS/JS plano o un framework que compile a archivos estáticos (Vite, CRA, Astro): despliega como un sitio estático (build_command, output_dir). Prefiere esto sobre ejecutar un servidor para una página estática.
  • Cualquier cosa con un servidor: un servicio con build_command, run_command, http_port. Los diseños estándar funcionan sin configuración: Node necesita package.json con un script start; Python necesita requirements.txt y un comando de ejecución (p.ej. gunicorn app:app --bind 0.0.0.0:$PORT). Un Dockerfile en el repositorio también funciona (establece dockerfile_path), pero no puedes probar la compilación aquí, así que mantenlo simple.
  • Sin disco persistente. Cualquier cosa escrita en el sistema de archivos se pierde en cada despliegue. No uses archivos SQLite, subidas a disco o archivos JSON para estado. Si la aplicación necesita una base de datos, añade una Postgres gestionada (ver más abajo); de lo contrario, mantenla sin estado.
  • Ejecútala localmente primero: npm install, iníciala con PORT=8080, y haz curl localhost:8080. Publica solo lo que funciona.
  • Añade un .gitignore con node_modules etc. No incluyas secretos en el commit. Nunca imprimas tokens.

Si la aplicación usa Postgres

El ${db.DATABASE_URL} de App Platform incluye sslmode=require, y el pg de Node lo rechaza con SELF_SIGNED_CERT_IN_CHAIN. Elimina sslmode de la URL y establece ssl: { rejectUnauthorized: false }:

function normalizeDatabaseUrl(raw) {
  const u = new URL(raw);
  u.searchParams.delete("sslmode"); u.searchParams.delete("ssl");
  return u.toString();
}
const pool = new Pool({ connectionString: normalizeDatabaseUrl(process.env.DATABASE_URL), ssl: { rejectUnauthorized: false } });

Añade "databases": [{"name":"db","engine":"PG","production":false,"version":"16"}] a la especificación y {"key":"DATABASE_URL","scope":"RUN_TIME","value":"${db.DATABASE_URL}"} a cada componente que lo necesite. Reintenta la primera conexión durante ~30 s: la base de datos puede estar aún iniciándose. Solo añade una base de datos si la aplicación realmente la necesita (cuesta extra).

2. Proponer el plan y preguntar (detente aquí hasta que digan que sí)

Una vez que la aplicación se compile y ejecute localmente, detente y envía al usuario un plan breve, luego espera una respuesta. No ejecutes ninguna llamada bk, git push ni creación de repositorio/aplicación hasta que lo apruebe. Incluye:

  1. Lo que has construido y que se ejecutó localmente (una línea).
  2. Los pasos que vas a seguir, en orden, cada uno con lo que toca:
    • Crear repositorio GitHub jnaidu-do/<nombre> — público, en la cuenta compartida jnaidu-do (no la cuenta propia del usuario)
    • Subir los archivos de la aplicación con git (enumera lo que se incluye; confirma que no hay secretos)
    • Crear aplicación de App Platform <nombre> en la región nyc: un sitio estático (sin coste para unos pocos) o un servicio (basic-xxs, unos $5/mes) — di cuál y el coste
    • Esperar la compilación (3–5 min), luego dar la URL pública
  3. Qué se vuelve público: el repositorio y el sitio en vivo son legibles por cualquiera en internet.
  4. Qué no harás sin preguntar de nuevo: subir más cambios, editar la especificación o eliminar nada.

Termina con una pregunta directa, p.ej. "¿Creo el repositorio y la aplicación como se indica? También puedes cambiar el nombre, hacer el repositorio privado (eso necesita que se conceda la GitHub App de DigitalOcean), o decir que no." Ofrece alternativas si corresponde (estático vs servicio, nombre diferente). Si declina, entrega los archivos o el código listo para el repositorio y detente.

Pregunta de nuevo, por separado, antes de: cualquier git push posterior (actualizaciones), cambiar la especificación de la aplicación y eliminar la aplicación.

3. Subir a GitHub

Usa el nombre que propusiste en el paso 2 (qm-<nombre-corto-en-kebab>) (minúsculas). Si el nombre ya existe (422), añade un sufijo corto.

# crear el repositorio (público: App Platform puede clonarlo sin autenticación adicional)
bk github POST https://api.github.com/user/repos '{"name":"qm-hello-site","private":false}'

# subir desde un directorio limpio nuevo que contenga la aplicación (sin node_modules)
cd "$(mktemp -d)" && cp -r /path/to/app/. . && git init -q -b main && git add . &&
git -c user.name=QM -c [email protected] commit -qm "Initial commit" &&
git remote add origin "$AGENT_API_URL/v1/credentials/git/github-git/jnaidu-do/qm-hello-site.git" &&
git -c http.extraHeader="x-agent-capability: $AGENT_CREDENTIAL_TOKEN" push origin HEAD:refs/heads/main

Actualizaciones posteriores: haz commit y push de la misma manera (HEAD:refs/heads/main), pero solo después de informar al usuario qué cambió y obtener un sí — cada push es una nueva aprobación, no cubierta por el primer plan.

4. Crear la aplicación en App Platform

bk digitalocean POST https://api.digitalocean.com/v2/apps '{"spec": {
  "name": "qm-hello-site",
  "region": "nyc",
  "services": [{
    "name": "web",
    "github": {"repo": "jnaidu-do/qm-hello-site", "branch": "main", "deploy_on_push": true},
    "build_command": "npm install",
    "run_command": "npm start",
    "http_port": 8080,
    "instance_size_slug": "basic-xxs",
    "instance_count": 1
  }]
}}'

Sitio estático en lugar de services: "static_sites": [{"name":"web","github":{...},"build_command":"npm run build","output_dir":"dist"}] (HTML plano: omite build_command, output_dir es "/"; para una aplicación de una sola página añade "catchall_document":"index.html").

La respuesta contiene el id de la aplicación. Guárdalo. Los nombres de aplicación son minúsculas, dígitos, guiones, de 2 a 32 caracteres.

Si la creación falla con un error de acceso al repositorio de GitHub ("does not have access to the repository"): la GitHub App de DigitalOcean no tiene acceso a este repositorio. Dile al usuario que abra https://github.com/apps/digitalocean/installations/select_target y conceda acceso a los repositorios de jnaidu-do, luego dile que lo haga; reintenta la misma solicitud. Si no puede, usa como alternativa una fuente de clonación pública genérica ("git": {"repo_clone_url": "https://github.com/jnaidu-do/qm-hello-site.git", "branch": "main"}) y dile que las actualizaciones entonces necesitarán un redespliegue (ver más abajo).

5. Esperar a que esté ACTIVA

Una compilación real tarda de 3 a 5 minutos. Pregunta cada 30 s, y dile al usuario que estás esperando:

bk digitalocean GET "https://api.digitalocean.com/v2/apps/$APP_ID/deployments?per_page=1" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{const d=JSON.parse(s).deployments[0];console.log(d.id,d.phase,d.progress?.success_steps+"/"+d.progress?.total_steps)})'

Fases: PENDING_BUILD → BUILDING → DEPLOYING → ACTIVE. ERROR o CANCELED significa que falló; ve a "Cuando falla". Deja de preguntar después de unos 12 minutos e informa al usuario del estado.

6. Informar de la URL

bk digitalocean GET "https://api.digitalocean.com/v2/apps/$APP_ID" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>console.log(JSON.parse(s).app.live_url))'

Prueba con curl -s -o /dev/null -w '%{http_code}' <live_url> — espera 200. Este ordenador a menudo no puede alcanzar sitios nuevos *.ondigitalocean.app (la conexión falla). Si curl falla pero el despliegue está ACTIVO, eso no es un fallo de despliegue: di que no se pudo verificar desde aquí y pide al usuario que abra la URL. Dale al usuario la URL completa https://…ondigitalocean.app y la URL del repositorio. La aplicación es pública en internet.

Cuando falla

Lee los logs tú mismo; no le pidas al usuario que los proporcione. Obtén una URL de log, luego recupérala:

bk digitalocean GET "https://api.digitalocean.com/v2/apps/$APP_ID/deployments/$DEPLOYMENT_ID/logs?type=BUILD"
# el cuerpo tiene "live_url" (o "historic_urls"); haz curl a esa URL (sin autenticación)

type es BUILD, DEPLOY o RUN (los sitios estáticos solo tienen BUILD). Empareja la señal:

SeñalSolución
Missing script: start / comando no encontradobuild_command / run_command incorrectos, o falta el script start
El contenedor se cierra, la verificación de salud fallano está escuchando en $PORT / no vinculado a 0.0.0.0, o falla al arrancar
SELF_SIGNED_CERT_IN_CHAINFalta el ayudante SSL de Postgres (ver arriba)
Cannot find moduleFalta dependencia en package.json, o node_modules incluido en el commit no coincide
Vite "Blocked request. This host is not allowed"establece preview: { allowedHosts: true } (o sirve el dist compilado como sitio estático)
Compilación "skipped" / ACTIVA en segundosla fuente no cambió; haz un commit real

Dile al usuario la causa raíz (la línea exacta del log) y la solución que propones, y pregunta antes de subir la corrección o cambiar la especificación — cada push y cada cambio de especificación necesita su propio sí. Después de que lo apruebe, corrige el código, haz commit y push (deploy_on_push redespliega automáticamente), luego vuelve a preguntar. Para un cambio de especificación, envía PUT /v2/apps/$APP_ID con {"spec": {...}} (obtén la especificación actual primero con GET /v2/apps/$APP_ID y edítala). Para una fuente git genérica, fuerza una recompilación con POST /v2/apps/$APP_ID/deployments con cuerpo {"force_build":true}. No elimines y recrees la aplicación para solucionar un fallo — eso cambia la URL. Soluciona hacia adelante. Ríndete después de tres intentos fallidos diferentes e informa de lo que probaste. Leer logs no necesita aprobación.

Eliminación

Solo cuando el usuario lo pida explícitamente, y nombra la aplicación primero. DELETE /v2/apps/$APP_ID elimina la aplicación. El repositorio de GitHub se deja atrás (esta credencial no puede eliminar repositorios); dilo.

Informe

Termina con: la URL en vivo, la URL del repositorio y una línea sobre cómo actualizarlo ("pídeme que cambie X y lo subiré").

Skills relacionados