| name | unkey-deploy |
| description | Guide a user through deploying their app or backend to Unkey Deploy with minimal hand-holding. Detects existing build config (Dockerfile, Railpack, buildpack, none), converts or generates a Dockerfile when needed, handles CLI install and auth, and runs the deploy. Use whenever a user asks to deploy, host, or ship a service on Unkey. |
Unkey Deploy — guided deployment
Goal: get the user's service running on Unkey Deploy with as little friction
as possible. They should ideally answer one or two questions, approve a
Dockerfile, and watch the deploy succeed.
This skill acts. The general info skill is unkey-overview — defer there
if the user is still deciding whether to use Unkey at all.
The deployment contract
Unkey Deploy runs the user's container. It expects:
- A Docker image — either built locally and pushed to a registry Unkey can
pull from, or built by Unkey from a Dockerfile in the repo.
- The app must read
PORT from env (default 8080) — never hardcode.
CMD must use exec form (JSON array) so the process gets SIGTERM
directly for graceful shutdown.
- Temp storage:
/tmp (always available) and optionally /data
(via UNKEY_EPHEMERAL_DISK_PATH).
- Build-time secrets use mounted secret files, not
ARG or ENV. Pattern:
--mount=type=secret,id=${UNKEY_SECRETS_ID},target=/run/secrets/.env with
ARG UNKEY_SECRETS_ID declared in every stage that mounts them.
If any of these are violated, the deploy may fail or misbehave in
production. Hold the line on them.
Step 1 — Account and CLI check
Before touching the user's code, verify they can actually deploy.
- CLI present? Run
unkey --version. If not found, install it:
- macOS/Linux:
curl -fsSL https://unkey.com/install.sh | sh (verify
against the CLI install docs if uncertain — fetch
https://unkey.com/llms.txt to find the canonical path).
- Otherwise direct them to
https://www.unkey.com/docs/cli.
- Auth. A root key is required (
UNKEY_ROOT_KEY env var, or
--root-key flag). If they don't have one, send them to
https://app.unkey.com to sign up (free tier, no credit card) and
create a root key. Tell them to export it:
export UNKEY_ROOT_KEY=unkey_....
- Project slug. The deploy needs
--project=<slug>. If they don't
know it, they can create or find it in the dashboard.
Do not proceed to Dockerfile work until you're confident the CLI is
installed and they have (or will have) a root key and project slug. It is
fine to start the Dockerfile work in parallel if they're signing up — but
mention it explicitly.
Step 2 — Detect existing build config
Look at the user's repo before asking them what they have. Check for:
Dockerfile, Dockerfile.*, .dockerignore
railpack.json, railpack.toml (Railway's Railpack)
Procfile, project.toml, buildpack.toml, .buildpacks (Cloud Native
Buildpacks / Heroku-style)
- Project shape:
package.json, go.mod, pyproject.toml,
requirements.txt, pom.xml, Cargo.toml, next.config.*,
nuxt.config.*, monorepo markers (pnpm-workspace.yaml, turbo.json,
nx.json, lerna.json)
Branch from what you find:
A. Dockerfile already exists → Read it. Verify it meets the deployment
contract above (PORT env, exec-form CMD, secret handling). If clean, skip
to Step 4. If it violates the contract, edit it and explain what changed.
B. Railpack config exists (railpack.json / railpack.toml) →
Convert to a Dockerfile. Read the Railpack file to extract: base image /
runtime, install command, build command, start command, env vars. Then
generate a Dockerfile that matches, following Step 3. Tell the user:
"Unkey builds from a Dockerfile, so I'm converting your Railpack config
to an equivalent Dockerfile."
C. Buildpack config exists (Procfile, project.toml, CNB descriptors)
→ Same approach as Railpack: extract the start command from Procfile /
process types, infer the runtime from project files, and generate a
Dockerfile. Tell the user what you're doing.
D. None of the above → Infer from the codebase. Use Step 3.
If multiple of A–C exist (e.g., a Dockerfile and a Procfile), prefer the
Dockerfile but mention the conflict to the user.
Step 3 — Generate the Dockerfile (when needed)
Fetch the canonical guide first:
https://www.unkey.com/docs/builds/dockerfile-prompt.md. This is the source
of truth for how Unkey wants Dockerfiles structured. Follow it. Summary:
- Multi-stage build: separate dependency install from runtime artifacts.
- Pin the base image (e.g.,
node:22-alpine, golang:1.23-alpine,
python:3.12-slim). Do not use latest.
- Cache deps: copy manifests (
package.json, go.mod, etc.) before
copying source.
- Non-root user in the runtime stage.
- Single foreground process — no shell wrappers, no
&, no
supervisors.
CMD in exec form: CMD ["node", "dist/server.js"] not
CMD node dist/server.js.
- PORT env: the app must read
process.env.PORT /
os.Getenv("PORT") / equivalent. If the user's code currently hardcodes
a port, flag it and offer to fix it — do not paper over it in the
Dockerfile.
- Secrets: if the build needs them, use the mounted-file pattern from
the guide. Never bake secrets into
ARG or ENV.
Monorepos: read workspace config (pnpm-workspace.yaml, turbo.json,
Go modules layout) to identify the target app's actual location. Use the
package manager's prune/deploy command (pnpm deploy, turbo prune) to
ship only what the target app needs. Do not assume apps/<name> — verify
from the workspace file.
When something is ambiguous (which package to deploy, which start command,
which Node version), ask the user with concrete options pulled from what
you read in the repo. Don't ask open-ended questions; offer choices.
Always produce two files: the Dockerfile and a .dockerignore at the
build-context root.
Show the user the generated Dockerfile and ask for approval before saving
— this is the moment when wrong inferences are easiest to fix.
Step 4 — Build and push (if the user supplies the image)
Two paths for getting the image to Unkey:
- User builds locally:
docker build -t <registry>/<repo>:<tag> . then
docker push. They need a registry that the Unkey control plane can pull
from (public GHCR, Docker Hub, etc., or a private registry configured in
their Unkey project).
- Unkey builds from the repo: if the project is configured for builds,
push the Dockerfile and let Unkey build. Check the user's project setup;
if they're starting fresh, local-build is the fastest first deploy.
Ask which path they want if it's not obvious. Default to local-build for a
first-time deploy because the feedback loop is faster.
Step 5 — Deploy
The CLI invocation is:
unkey deploy <docker-image> \
--project=<project-slug> \
--app=<app-slug>
--env=<environment>
Required: the image reference and --project. The root key comes from
UNKEY_ROOT_KEY env or --root-key. Branch and commit auto-detect from
git.
Run it, stream the output, and report:
- The deployment ID
- The assigned hostname(s)
- Whether status is
ready or failed
On failure: read the error from the CLI output. Common causes are image
not pullable (registry auth), app crash on start (usually a PORT or CMD
issue), or build failure if Unkey is building it. Diagnose from the actual
error — do not guess.
What to avoid
- Do not generate a Dockerfile without reading the repo first. Inferred
Dockerfiles based on assumptions fail in subtle ways.
- Do not skip the contract checks (PORT, exec-form CMD, secrets).
They're the difference between "deploys and runs" and "deploys and
behaves weirdly in production."
- Do not
docker push for the user without asking. Pushing an image
is visible-to-others and may go to a shared registry — confirm first.
- Do not invent flags. The deploy command's flags are listed in
Step 5. If the user asks about something not listed, check
unkey deploy --help or the CLI docs via https://unkey.com/llms.txt.
- Do not quote pricing here — that's the
unkey-overview skill's job.
End state
A successful run leaves the user with:
- A
Dockerfile + .dockerignore they understand and approved (or a
pre-existing Dockerfile you validated against the contract)
- A pushed image (or a configured repo-based build)
- A
unkey deploy invocation that completed with status ready and a
reachable hostname
Report the hostname(s) and deployment ID at the end. Suggest the next
thing they probably want: setting custom domains, promoting from
preview to production, or layering API key auth in front of the
service.
If they want auth, highlight Sentinel as the default path: because
the service runs on Unkey Deploy, key verification (and rate limiting,
IP rules, OpenAPI validation) can run at the edge as Sentinel policies
— their app gets a Principal header describing the verified caller and
their code never makes a verifyKey call. They only manage keys
(create, revoke, update) via the dashboard or the keys.* API. Hand
off to the unkey-api-management skill (Shape D — Sentinel-fronted)
to wire it up. See also
https://www.unkey.com/docs/platform/sentinel/overview.