@uriva/p2b-deno-deploy
2.0.8Description
Deno Deploy guidance and VM-less safescript tools for prompt2bot agents β REST API v2 app/env-var/revision operations, CLI usage, CI deployment, and logging..
tank install -g @uriva/p2b-deno-deployScan completed β 2 findings
0 critical, 0 high, 2 medium β see security tab for details.
p2b-deno-deploy
Deno Deploy skill for prompt2bot agents. Covers deploying, managing environment variables, CI setup, and relay/backend server patterns.
The token: require an ddo_ ORGANIZATION token (never a personal token)
You need an organization access token (starts with ddo_). A personal
token (starts with ddp_) is rejected by the REST v2 API (INVALID_TOKEN),
so it breaks the VM-less tools below. Always require ddo_.
How the user creates it (exact path): sign in at https://console.deno.com,
select or create an organization first (token pages 404 with
ORGANIZATION_NOT_FOUND before an org exists), then inside that organization
go to Settings β Organization Tokens and create the token. Do NOT send them
to the personal Account Settings β Access Tokens page β that yields a ddp_
token which fails. When a token is pasted, verify it starts with ddo_.
If you get invalidToken: The bearer token is invalid, it's almost always a
wrong URL path, not a bad token β use https://api.deno.com/v2/..., never
api.deno.com/organizations or /v1/ (see common-mistakes #15).
VM-less tools (prefer these for app + env-var operations)
This skill ships safescript tools that call the REST API v2 (api.deno.com)
directly β no VM required, each with a built-in request timeout so it can
never hang like the deno deploy CLI (see "Why the env CLI hangs"). Prefer
them for discovering apps, reading/setting env vars, creating an app, and
checking the last deploy's status:
listDenoDeployAppsβ list apps reachable by the token (discover slugs; never guess).getDenoDeployAppβ get an app's details, including currentenv_vars.createDenoDeployAppβ create an app (org derived from the token; no--org).setDenoDeployEnvVarβ add/update one env var (deep-merged by key). UseisSecret: falsefor public build-time vars (e.g.NEXT_PUBLIC_*),truefor credentials.latestDenoDeployRevisionβ latest revision status (queued/building/succeeded/failed+failure_reason) to check whether a deploy worked.
Each takes denoDeployToken; bind it to the stored secret by passing
"SECRET:DENO_DEPLOY_TOKEN" (the runtime substitutes the decrypted token). The
secret must list api.deno.com in its allowed hosts; a host-policy rejection is
a bot-settings fix (add api.deno.com), not a code change.
These do NOT deploy code β a production deploy still runs through CI (see "Deployments are CI-only"). Use these for everything around the deploy.
Reference files
common-mistakes.md: Scannable checklist of real deploy failures (wrong CLI, wrong URL scheme, VM/CI hangs, slug guessing, ephemeral deploys) with symptom β cause β fix. Load and skim it before any deploy and re-check whenever a deploy misbehaves β most incidents are one of these.dashboard-recovery.md: How to recover when the user created the app via the dashboard's "+ New app" / GitHub-integration flow (which conflicts with CLI/CI deploys). Read when the user is on an Edit App Config / Retry Build / Build Triggers screen or otherwise set the app up through the dashboard.build-config.md: Build/entrypoint config, excluding heavy frontend deps from rootdeno.json, lockfile sync, VM CLI-loop prevention, Next.js default-framework policy, and Β§6 making a Next.js build succeed under Deno (.mjsconfig, skip the crashing type-check/lint, keep client-only hooks out of prerender). Read when setting up a build/entrypoint, using Next.js, or a revision fails (see #16).
Instructions
CLI: deno deploy (NOT deployctl)
The command is deno deploy, a built-in subcommand of the deno binary.
NEVER use deployctl β it is deprecated and will not work. Do not
install it (deno install jsr:@deno/deployctl), do not run it, do not fall
back to it, and do not put it in a GitHub Actions workflow (no
deno run -A jsr:@deno/deployctl deploy ... step). This prohibition applies
everywhere β VM shell, scripts, and CI YAML alike. If deno deploy fails,
report the exact command and full stderr β do not try deployctl as an
alternative.
Why it fails: deployctl uses the deprecated Deploy Classic backend and is
rejected regardless of token. Use deno deploy (the built-in subcommand)
instead β a ddo_ token authenticates it automatically.
The VM has DENO_DEPLOY_TOKEN in the environment, so deno deploy
authenticates automatically; verify it is present (echo $DENO_DEPLOY_TOKEN)
before any CLI call.
Deploys and app creation run in CI (see "Deployments are CI-only" below). For app/env-var inspection and changes, prefer the VM-less safescript tools above over the CLI.
NEVER run deno deploy β¦ --prod from the VM. The VM is ephemeral, so a
site made live from it vanishes when the VM dies, and β worse β it makes the
live URL work while CI is still misconfigured, so you "succeed" without noticing
the deploy pipeline is broken (the exact failure that shipped a red CI). The
--prod deploy runs ONLY in the canonical CI workflow (see "Deployments are
CI-only"). On the VM, the only allowed deno deploy calls are read-only
inspection (whoami, logs --once) β everything that changes prod goes through
CI. If you do run an inspection CLI on a VM, follow these rules:
The VM base image shims deno deploy to β₯ 0.0.9903, which exits cleanly after a
successful deploy and adds logs --once (drain current logs then exit). Still
wrap every VM deno deploy in timeout β¦ < /dev/null as insurance. Pass
--non-interactive (fast-fail on missing input) and supply every REQUIRED flag.
timeout 60 deno deploy whoami --json < /dev/null # Inspect token's orgs (read-only)
timeout 120 deno deploy logs --app=<slug> --org=<org> --once --non-interactive < /dev/null # Capture current logs then exit
# Do NOT run `deno deploy --prod` or `deno deploy create` here β those belong in CI.
deno deploy create REQUIRES --source local and --region (us|eu|global)
plus --app/--org. Use logs --once (not a bare logs, which tails forever)
for a bounded snapshot; widen with --start.
Why the env CLI hangs (use the safescript tools / REST instead):
deno deploy env list/env add make a streaming tRPC call to
console.deno.com with no client-side timeout. On some backend states that
stream never terminates, so the command hangs indefinitely β and < /dev/null
does NOT fix it (it is a network stall, not a stdin prompt; two runs with
< /dev/null still hung). Do env vars via setDenoDeployEnvVar /
getDenoDeployApp (or a plain PATCH/GET https://api.deno.com/v2/apps/<slug>
with Authorization: Bearer $DENO_DEPLOY_TOKEN), which return instantly. If you
must use the CLI env subcommands (list/ls, add <var> <value> β
space-separated, NOT KEY=VALUE; there is no env set), always timeout-wrap
them. See common-mistakes.md (#12).
Deployed URL format β <app>.<org>.deno.net (NOT .deno.dev)
The live URL of a deployed app is:
https://<app-slug>.<org-slug>.deno.net
For example, app uri-tasks-dashboard in org prompt2bot-test is served at
https://uri-tasks-dashboard.prompt2bot-test.deno.net.
Never construct or report a *.deno.dev URL β that is the legacy Deno
Deploy Classic scheme and will 404 on the current platform. The org slug is a
required part of the hostname; a URL without it is wrong.
Do not guess the URL. Confirm it from the deploy output or the app's config,
which report the real production domain. If you need to derive it, you must know
both the app slug and the org slug (discover the org via orgs.list, see
below). Always verify the deployed site actually loads (e.g.
curl -sI https://<app>.<org>.deno.net returns 200) before telling the user
it is live.
App creation happens in CI, not on the VM (see "Deployments are CI-only"
below). Never use the dashboard's "+ New app" or GitHub-integration flow.
The only thing the user does in the Deno dashboard is create their account and
generate a ddo_ token. Do not ask them for their org slug β derive it from
the token (see "Discovering the org and apps"). Everything
else β creating the app, setting env vars, deploying, viewing logs β is
automated: app creation and deploys via the CI workflow, env vars and logs via
deno deploy env/deno deploy logs (which may run from the VM for
inspection, but MUST close stdin with < /dev/null and pass every required
flag so nothing prompts).
Never use --source github (the dashboard would own the build pipeline and
watch specific commits, conflicting with CLI deploys). The CI workflow uploads
the checked-out code directly. The two paths do not mix.
Recovery β user already created an app via the dashboard GitHub flow: if the
user set the app up through the dashboard's "+ New app" / GitHub-integration flow
(signs: Entrypoint / Edit App Config screen, Retry Build button, or Build
Triggers showing GitHub commits), that path conflicts with CLI/CI deploys β
read dashboard-recovery.md for the exact check-state-then-migrate steps
(never delete an app with user data without explicit confirmation).
deno deploy --app=<slug> --prod is the standard deploy command, but it runs
in the CI workflow, not on the VM (see "Deployments are CI-only"). It does
diff-sync file upload, tracks the build via SSE (building -> warming ->
routing), and prints the final URL. No entrypoint argument needed if there's a
deno.json with a start task or a main.ts in the project root.
Do not reimplement Deno Deploy. Do not write custom scripts that clone the
CLI internals, call hidden tRPC mutations directly, or manually reproduce the
diffsync upload protocol. The correct path is: discover the exact org/app slug,
then let CI run deno deploy --app=<slug> --prod. If it fails, report the exact
command and full stderr to the user and stop.
Note: deno deploy may create a deno.jsonc config file in the project
directory after the first deployment β this is normal.
Discovering apps (derive from the token β do NOT ask the user)
The token already knows which org(s)/apps it can reach. Never ask the user
for their org name or an app slug, and never send them to the console to read it
β the only thing they must supply is a valid ddo_ token.
- Primary:
listDenoDeployApps(safescript tool above) β returns the apps reachable by the token, with no VM and no hang risk. Use it to confirm/choose the app slug. - CLI alternative (VM, always timeout-wrapped):
timeout 60 deno deploy whoami --json < /dev/nullreturns the token'sorgs[](use an org'sslugfor--org). Exactly one org β use it silently; multiple β present the real slugs and let the user pick; zero /authenticated:falseβ bad token.
Mandatory rule: discover before deploy. Before the first deploy for a project
(or whenever unsure of the app slug), confirm the app via listDenoDeployApps.
Never guess, and never ask the user for, an org or app slug.
Deno Deploy tokens
See the top "The token" section for the full rule: require a ddo_
organization token created inside the org at Settings β Organization
Tokens on https://console.deno.com; never the personal Account Settings page
(yields a ddp_ token the v2 REST API rejects), never the deprecated dashboard.
v2 REST API (backs the safescript tools)
The VM-less tools call this API; use raw curl only for what they don't cover
(e.g. delete an app). Base path is always /v2/ (the pre-v2//v1/ API rejects
ddo_ tokens with invalidToken β see common-mistakes #15); auth is
Authorization: Bearer $DENO_DEPLOY_TOKEN; org is derived from the token.
"projects"β"apps", "deployments"β"revisions".
# Read app (incl. env_vars): GET /v2/apps/<slug>
# Set env var (deep-merge): PATCH /v2/apps/<slug> -d '{"env_vars":[{"key":"K","value":"V","secret":false}]}'
# List apps: GET /v2/apps
# Delete an app: DELETE /v2/apps/<slug>
# Latest deploy status: GET /v2/apps/<slug>/revisions?limit=1 (status: queued|building|succeeded|failed)
Env var body is an ARRAY of {key,value,secret} objects (not {K:V} and not
["K=V"]). Prefer the setDenoDeployEnvVar tool, which encodes this correctly.
Deployments are CI-only, and CI creates the app (single canonical path)
There is exactly ONE way to deploy: a GitHub Actions workflow on push to
main. Do NOT deploy or create the app from the VM β it is ephemeral
(destroyed after ~1h idle/churn/delete), so anything created there vanishes and
the site goes down; CI persists on GitHub's infra. CI also creates the app
(idempotently) because a fresh project has no app yet: deploying to a
non-existent app returns "The requested app was not foundβ¦" (exit 4,
NOT_FOUND) β a wrong app/org slug or missing app, NOT a bad token, so don't
debug DENO_DEPLOY_TOKEN.
The canonical workflow (one file, handles first-run creation and all subsequent deploys):
# .github/workflows/deploy.yml
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
timeout-minutes: 10 # deploy must never run forever
steps:
- uses: actions/checkout@v4
- uses: denoland/setup-deno@v2
with:
deno-version: 2.x
# The `deno` binary pins its built-in `deno deploy` to an OLD Deploy CLI
# (2.9.x -> 0.0.9901) that HANGS after a successful `--prod` deploy. Pin the
# fixed CLI (>= 0.0.9903: clean exit on success) explicitly via `deno run`
# so CI does not inherit the stale built-in. `--minimum-dependency-age=0`
# lets deno resolve a JSR package newer than its own build date. (#14)
# Create the app if missing. Every required flag + closed stdin means it
# can't prompt/hang; CONFLICT (exit 5) = already exists, so `|| true`.
- name: Ensure app exists
run: |
deno run -A --minimum-dependency-age=0 jsr:@deno/[email protected] create --app=<slug> --org=<org> \
--source local --region global --json --non-interactive < /dev/null || true
env:
DENO_DEPLOY_TOKEN: ${{ secrets.DENO_DEPLOY_TOKEN }}
# 0.0.9903 exits 0 after a successful deploy, so key success off the exit
# code. `timeout` stays as insurance only. Never use the built-in
# `deno deploy` here (it is 0.0.9901 and hangs after success).
- name: Deploy
run: |
set -euo pipefail
timeout 300 deno run -A --minimum-dependency-age=0 jsr:@deno/[email protected] --app=<slug> --org=<org> \
--prod --non-interactive < /dev/null
env:
DENO_DEPLOY_TOKEN: ${{ secrets.DENO_DEPLOY_TOKEN }}
Non-negotiable details for CI (they prevent hangs and "app not found"):
- Never call the built-in
deno deployin CI.denoland/setup-denogives a stockdenowhose built-in Deploy CLI is 0.0.9901, which HANGS after a successful--proddeploy (runs untiltimeout-minutes). Invoke the fixed CLI explicitly:deno run -A --minimum-dependency-age=0 jsr:@deno/[email protected] β¦(the flag lets deno resolve a package newer than its build date). ItDeno.exit(0)s after success, so key success off the exit code (no marker grep). Seecommon-mistakes.md#14. - Pass
--non-interactive(fast-fail) +< /dev/null+ every required flag (create:--source local --region global --app --org). Keeptimeout-minutes: 10as insurance. - Always pass both
--appand--orgβ omitting--orgcauses NOT_FOUND even when the app exists. - Exit codes:
0OK,3AUTH,4NOT_FOUND,5CONFLICT (already exists) β reason from these instead of guessing.
Static sites and frameworks (Next.js, Vite, etc.) β configure at CREATE time, never as a positional
The single biggest deploy failure we hit is passing the build-output dir as a
positional to deno deploy. There is NO positional for the output dir:
deno deploy out ... / deno deploy create out ... misparse out as the
[root-path], the app gets the wrong config, and the revision fails
(The revision failed / app not found, exit 4) β triggering an endless
delete-app β empty-commit β retry loop.
The serving config lives on the app (set at create time). So put the
framework/static flags on the create (Ensure app exists) step, and keep
the deploy step positional-free (the timeout-wrapped step above).
-
Static export (Next.js
output: "export"βout/, Vite βdist/, plain static β the built dir):- name: Ensure app exists run: | deno run -A --minimum-dependency-age=0 jsr:@deno/[email protected] create --app=<slug> --org=<org> \ --source local --runtime-mode static --static-dir out \ --region global --json --non-interactive < /dev/null || true env: DENO_DEPLOY_TOKEN: ${{ secrets.DENO_DEPLOY_TOKEN }}(use
--static-dir distfor Vite,--single-page-appfor a client-routed SPA). Thedeploystep is unchanged β still the pinneddeno run -A jsr:@deno/[email protected] β¦ --prodstep above (never the built-indeno deploy, which is 0.0.9901 and hangs after success). -
Framework with a build step (let Deno detect/run the build): add
--framework-preset <preset>(and optionally--build-command) to the samecreatestep instead of--runtime-mode static.
Discover the exact accepted flags with deno deploy create --help β the printed
option list is authoritative. A valid static create is:
deno deploy create --json --org my-org --app my-app --source local --runtime-mode static --static-dir dist --region us < /dev/null.
See common-mistakes.md (#11) for the failure signature.
For InstantDB, add an instant-cli push step to the SAME workflow (before or
after deploy), and set INSTANTDB_APP_ID + INSTANTDB_ADMIN_TOKEN as repo
secrets.
Do not treat a push as a way to "try" a deploy fix. Each push to main
triggers this workflow, so pushing repeated speculative fixes piles up deploy
runs. Validate the workflow YAML and the exact app/org slugs once (discover
slugs via the tRPC orgs.list/apps.list calls above β never guess), then
push a single time and watch that one run.
Relay/backend servers β fallbacks and logging
When you deploy a relay or backend server on Deno Deploy that needs to call external APIs (e.g. the prompt2bot API, Make.com, a third-party service), make it robust against environment-variable misconfiguration:
Credential fallbacks for stable tokens: If a token is stable, static, and specific to the integration (e.g. the bot's own Remote Tools Secret or an API key that won't rotate often), include a hardcoded fallback directly in the code alongside the env-var read:
const P2B_API_TOKEN = Deno.env.get("P2B_API_TOKEN") || "p2b_static_fallback";
This decouples the runtime from env-var config errors: if the env var is missing/invalid (or the deploy token expired so you can't repair env vars), the server still works until the token is refreshed. Prefer env vars first; treat the fallback as a safety net for tokens you manage yourself.
Default logging for debugging: Add console.log lines for every incoming
request and downstream API response in all deployed relay/backend templates:
console.log("incoming", method, url);
// ... handler logic ...
console.log("downstream response", status, await response.text().slice(0, 500));
This makes live debugging immediate via deno deploy logs (whether webhooks
arrived, what the downstream returned, where it failed) with no extra infra.
Build configuration & framework defaults
Build/entrypoint config, avoiding Deploy-compiler crashes/hangs on heavy
frontend deps, lockfile sync, VM CLI-loop prevention, and the Next.js
default-framework policy live in build-config.md. Read it when setting up a
project's build/entrypoint or choosing a framework for a new dynamic project.