Communitygithub.com

app-platform-publish

Publique um site ou aplicativo no DigitalOcean App Platform e retorne sua URL pública. Use esta ferramenta sempre que o usuário pedir para publicar, implantar, enviar, hospedar ou colocar no ar algo que você construiu. Use esta ferramenta EM VEZ da ferramenta `publish` ou `apps`, que requer Docker e não funciona aqui. Abrange o envio do código para o GitHub, a criação do aplicativo no App Platform, a espera pela implantação e a correção de falhas a partir dos logs de build.

O que é app-platform-publish?

Este skill permite que um agente de IA publique sites ou aplicativos no DigitalOcean App Platform sem a necessidade de Docker. Ele automatiza todo o fluxo: construir e testar o aplicativo no sandbox, torná-lo implantável (garantindo que escute na porta `$PORT`, use arquivos estáticos ou serviços, e que não haja dependência de disco persistente), propor um plano ao usuário para aprovação, enviar o código para o GitHub usando credenciais compartilhadas, criar o aplicativo no App Platform com a especificação correta (site estático ou serviço), e aguardar a implantação até que o status seja ACTIVE. Também inclui recuperação de falhas analisando logs de build (BUILD, DEPLOY, RUN) e aplicando correções como ajustar comandos de build/run, corrigir problemas de certificado SSL com Postgres, ou tratar requisições bloqueadas pelo Vite. Suporta Node.js (com npm), Python (com pip) e frameworks estáticos (Vite, CRA, Astro) e utiliza um helper de broker para chamar APIs do DigitalOcean e GitHub sem expor tokens. O skill é compatível com ambientes que possuem Node 24, Python 3.12, git e curl, mas não têm Docker, doctl ou jq.

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

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Publicar no App Platform

Fluxo: construir e testar no sandbox → tornar implantável → propor o plano e obter aprovação → enviar para o GitHub → criar o aplicativo no App Platform → aguardar ACTIVE → verificar a URL.

Nunca crie um repositório, faça push ou crie/atualize/exclua um aplicativo antes que o usuário tenha aprovado o plano na etapa 2. Um pedido como "publique isso" não é essa aprovação. Construir e testar no sandbox não precisa de aprovação.

Nunca use as ações publish, docker ou doctl – nenhuma delas funciona neste computador. Tudo passa por duas credenciais compartilhadas, que você chama através do broker de credenciais do núcleo. Você nunca vê os tokens.

Use um sandbox_id explícito para cada comando e selecione a credencial em execute.credentials para qualquer comando que chame o broker (digitalocean, github ou github-git). O núcleo fornece AGENT_CREDENTIAL_TOKEN apenas para esses comandos. Não use rm -rf; crie um diretório novo com mktemp -d.

O que o computador tem

Node 24 + npm, Python 3.12 + pip, git, gh (não logado; não o use), curl, gcc, make. Sem docker, doctl, jq, wget, unzip. Analise JSON com node -e ou python3 -c.

Credenciais (o que cada uma pode fazer)

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

Tudo é criado sob a conta compartilhada do GitHub jnaidu-do (uma credencial de toda a implantação, não a conta do próprio usuário). Diga isso no plano. Se o usuário quiser a própria conta, informe que isso ainda não foi configurado. Um 403 path_not_allowed / method_not_allowed significa que a requisição está fora disso – não tente variações; informe ao usuário que não é permitido.

Helper do broker

Cole isto uma vez por comando (o estado do shell não persiste). Ele imprime o status HTTP no stderr e o corpo da resposta no stdout:

bk() { # bk <credential> <METHOD> <url> [json-body]
  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. Tornar implantável (verifique antes de enviar)

O aplicativo é executado como um contêiner normal por trás do App Platform, não neste sandbox.

  • Escute em $PORT, vinculado a 0.0.0.0 (o App Platform define PORT; padrão 8080). Nunca codifique outra porta.
  • HTML/CSS/JS simples ou um framework que compila para arquivos estáticos (Vite, CRA, Astro): implante como um site estático (build_command, output_dir). Prefira isso a executar um servidor para uma página estática.
  • Qualquer coisa com um servidor: um serviço com build_command, run_command, http_port. Layouts padrão funcionam sem configuração: Node precisa de package.json com um script start; Python precisa de requirements.txt e um comando de execução (ex.: gunicorn app:app --bind 0.0.0.0:$PORT). Um Dockerfile no repositório também funciona (defina dockerfile_path), mas você não pode testar a compilação aqui, então mantenha simples.
  • Sem disco persistente. Qualquer coisa escrita no sistema de arquivos é perdida a cada implantação. Não use arquivos SQLite, uploads para disco ou arquivos JSON para estado. Se o aplicativo precisar de um banco de dados, adicione um Postgres gerenciado (veja abaixo); caso contrário, mantenha-o sem estado.
  • Execute localmente primeiro: npm install, inicie com PORT=8080 e curl localhost:8080. Publique apenas o que funciona.
  • Adicione um .gitignore com node_modules etc. Não confirme segredos. Nunca imprima tokens.

Se o aplicativo usar Postgres

O ${db.DATABASE_URL} do App Platform inclui sslmode=require, e o pg do Node então rejeita o CA gerenciado (SELF_SIGNED_CERT_IN_CHAIN). Remova sslmode da URL e defina 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 } });

Adicione "databases": [{"name":"db","engine":"PG","production":false,"version":"16"}] à especificação e {"key":"DATABASE_URL","scope":"RUN_TIME","value":"${db.DATABASE_URL}"} a cada componente que precisar. Repita a primeira conexão por ~30 s: o banco de dados ainda pode estar iniciando. Adicione um banco de dados apenas se o aplicativo realmente precisar (custa extra).

2. Propor o plano e perguntar (pare aqui até eles dizerem sim)

Depois que o aplicativo compilar e executar localmente, pare e envie ao usuário um plano curto, depois aguarde uma resposta. Não execute nenhuma chamada bk, git push ou criação de repositório/app até que eles aprovem. Inclua:

  1. O que você construiu e que foi executado localmente (uma linha).
  2. As etapas que você seguirá, em ordem, cada uma com o que toca:
    • Criar repositório GitHub jnaidu-do/<nome> — público, na conta compartilhada jnaidu-do (não a conta do próprio usuário)
    • Enviar os arquivos do aplicativo para ele com git (liste o que está incluído; confirme que não há segredos)
    • Criar aplicativo App Platform <nome> na região nyc: um site estático (sem custo por alguns) ou um serviço (basic-xxs, cerca de $5/mês) — diga qual e o custo
    • Aguardar a compilação (3–5 min), depois fornecer a URL pública
  3. O que se torna público: o repositório e o site ao vivo são legíveis por qualquer pessoa na internet.
  4. O que você não fará sem perguntar novamente: enviar mais alterações, editar a especificação ou excluir qualquer coisa.

Termine com uma pergunta direta, ex.: "Devo criar o repositório e o aplicativo como acima? Você também pode alterar o nome, tornar o repositório privado (isso precisa que o GitHub App do DO tenha acesso) ou dizer não." Ofereça alternativas se relevante (estático vs serviço, nome diferente). Se recusarem, forneça os arquivos ou o código pronto para repositório e pare.

Pergunte novamente, separadamente, antes de: qualquer git push posterior (atualizações), alterar a especificação do aplicativo e excluir o aplicativo.

3. Enviar para o GitHub

Use o nome proposto na etapa 2 (qm-<short-kebab-name>) (minúsculas). Se o nome já existir (422), adicione um sufixo curto.

# criar o repositório (público: o App Platform pode então cloná-lo sem autenticação extra)
bk github POST https://api.github.com/user/repos '{"name":"qm-hello-site","private":false}'

# enviar a partir de um diretório limpo e novo contendo o aplicativo (sem 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

Atualizações posteriores: commit e push da mesma forma (HEAD:refs/heads/main), mas apenas depois de informar ao usuário o que mudou e obter um sim — cada push é uma nova aprovação, não coberta pelo primeiro plano.

4. Criar o aplicativo 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
  }]
}}'

Em vez de services, para site estático: "static_sites": [{"name":"web","github":{...},"build_command":"npm run build","output_dir":"dist"}] (HTML simples: omita build_command, output_dir é "/"; para um aplicativo de página única adicione "catchall_document":"index.html").

A resposta contém o id do aplicativo. Mantenha-o. Nomes de aplicativos são letras minúsculas, dígitos, traços, 2–32 caracteres.

Se a criação falhar com um erro de acesso ao repositório do GitHub ("does not have access to the repository"): o GitHub App do DigitalOcean não tem acesso a este repositório. Diga ao usuário para abrir https://github.com/apps/digitalocean/installations/select_target e conceder acesso aos repositórios jnaidu-do, depois avise que está feito; repita a mesma requisição. Se não puderem, recorra a uma fonte de clone público genérica ("git": {"repo_clone_url": "https://github.com/jnaidu-do/qm-hello-site.git", "branch": "main"}) e avise que atualizações então precisam de uma reimplantação (veja abaixo).

5. Aguardar ACTIVE

Uma compilação real leva de 3 a 5 minutos. Pesquise a cada 30 s e avise ao usuário que está aguardando:

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 ou CANCELED significa que falhou; vá para "Quando falhar". Pare de pesquisar após cerca de 12 minutos e informe ao usuário onde está.

6. Relatar a 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))'

Tente curl -s -o /dev/null -w '%{http_code}' <live_url> — espere 200. Este computador muitas vezes não consegue alcançar novos sites *.ondigitalocean.app (a conexão falha). Se o curl falhar mas a implantação estiver ACTIVE, isso não é uma falha de implantação: diga que não foi possível verificar daqui e peça ao usuário para abrir a URL. Forneça ao usuário a URL completa https://…ondigitalocean.app e a URL do repositório. O aplicativo é público na internet.

Quando falhar

Leia os logs você mesmo; não peça ao usuário por eles. Obtenha uma URL de log e depois a acesse:

bk digitalocean GET "https://api.digitalocean.com/v2/apps/$APP_ID/deployments/$DEPLOYMENT_ID/logs?type=BUILD"
# o corpo tem "live_url" (ou "historic_urls"); acesse essa URL com curl (sem autenticação)

type é BUILD, DEPLOY ou RUN (sites estáticos têm apenas BUILD). Combine o sinal:

SinalCorreção
Missing script: start / comando não encontradobuild_command / run_command errado, ou nenhum script start
Contêiner sai, verificação de saúde falhanão está ouvindo em $PORT / não vinculado a 0.0.0.0, ou falha na inicialização
SELF_SIGNED_CERT_IN_CHAINHelper SSL do Postgres ausente (veja acima)
Cannot find moduledependência ausente em package.json, ou node_modules confirmado incompatível
Vite "Blocked request. This host is not allowed"defina preview: { allowedHosts: true } (ou sirva o dist compilado como site estático)
Build "skipped" / ACTIVE em segundosa fonte não mudou; faça um commit real

Informe ao usuário a causa raiz (a linha exata do log) e a correção que você propõe, e pergunte antes de enviar a correção ou alterar a especificação — cada push e cada alteração de especificação precisa de seu próprio sim. Após a aprovação, corrija o código, faça commit e push (deploy_on_push reimplanta automaticamente) e então pesquise novamente. Para uma alteração de especificação, envie PUT /v2/apps/$APP_ID com {"spec": {...}} (obtenha a especificação atual primeiro com GET /v2/apps/$APP_ID e edite-a). Para uma fonte git genérica, force uma recompilação com POST /v2/apps/$APP_ID/deployments com corpo {"force_build":true}. Não exclua e recrie o aplicativo para corrigir uma falha — isso muda a URL. Corrija adiante. Desista após três tentativas diferentes falhas e relate o que tentou. Ler logs não precisa de aprovação.

Excluindo

Apenas quando o usuário pedir explicitamente, e nomeie o aplicativo primeiro. DELETE /v2/apps/$APP_ID remove o aplicativo. O repositório no GitHub é deixado para trás (esta credencial não pode excluir repositórios); avise.

Relatório

Termine com: a URL ao vivo, a URL do repositório e uma linha sobre como atualizá-lo ("peça-me para mudar X e eu enviarei" ).

Habilidades Relacionadas