From deploio
Handles first-time deployment of an app to Deploio — from git URL to live HTTPS URL. This skill should be triggered when deploying a new app for the first time: "deploy my app on Deploio", "create a Deploio app", "how do I deploy to Deploio", "host on Deploio", "push to Deploio", "new app on Deploio", "first deploy to Deploio", "set up a new Deploio app", or setting up a new Deploio app from scratch. Covers auth, project setup, git credential resolution, buildpack/Dockerfile detection, and build monitoring. Do NOT use for apps already running on Deploio (use deploio-manage to update them).
How this skill is triggered — by the user, by Claude, or both
Slash command
/deploio:deploio-deployThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Your role is coordinator. You never run commands yourself — you spawn `deploio-cli` agents for all execution. Gather context via an agent, resolve blockers, confirm the plan with the user, then spawn an executor and a monitor agent in parallel.
Your role is coordinator. You never run commands yourself — you spawn deploio-cli agents for all execution. Gather context via an agent, resolve blockers, confirm the plan with the user, then spawn an executor and a monitor agent in parallel.
Communication style: Speak to the user in plain language — describe what will happen, never show raw nctl commands in your responses to the user. Agents manage the CLI entirely on the user's behalf. Keep tone calm and direct — avoid exclamations and over-eager phrases like "Great news!" or "Awesome!".
Before spawning any agent, evaluate these four conditions from what the user has already told you:
| Condition | Known if… |
|---|---|
nctl_known | User mentioned installing nctl, or ran it in this session |
remote_known | A git URL is visible in the conversation |
auth_known | User mentioned a Deploio project name or logged in |
framework_known | User said "Rails app", "Next.js", etc. |
If remote_known is false, run git remote get-url origin and git branch --show-current to discover the URL and branch. If a remote is found, derive app = <branch> (e.g. main), org from nctl auth whoami (the *-marked one — not the git URL), and project = <org>-<repo> (e.g. renuotest-myapp — never just <repo>; nctl errors). State: "I'll deploy project renuotest-myapp, app main, in organization renuotest — let me know if that's different." Only ask for a URL from scratch if no remote is configured.
Pass any known values into the gather-context spec as hints to speed up detection.
Spawn the deploio-cli agent with mode: bypassPermissions and this spec:
task: gather-context
hints:
remote_url: <from context, or null>
framework: <from context, or null>
nctl_installed: <from context, or null>
Constraints: Do not run any nctl commands during context gathering — nctl commands may require auth and will produce misleading errors before the project exists. Use only: git commands, file system reads, and nctl version.
Detection steps:
nctl version → set nctl_installed: true/false; require at least v1.16.0 for Deploio supportremote_url hint if provided; otherwise run git remote get-url origin → set remote_urlgit branch --show-current → set branchapp_type and port (framework details live in references/<FRAMEWORK>.md):| File present | app_type | default port |
|---|---|---|
Dockerfile | docker | read from EXPOSE line, else 8080 |
Gemfile containing rails | rails | 3000 |
package.json | nodejs | 3000 |
manage.py + requirements.txt | django | 8000 |
app.py or main.py + requirements.txt | flask | 8000 |
composer.json | php | 8080 |
go.mod | go | 8080 |
| none matched | unknown | null |
Detect git_auth_type:
remote_url starts with git@ → sshremote_url starts with https:// → httpsremote_url is null → noneCheck for multiple subdirectories each containing a Gemfile, package.json, or go.mod → set is_monorepo: true/false
Run nctl auth whoami (only if nctl is installed). Parse the output to find:
active_org: the organization marked with * in the list (e.g. * renuotest)available_orgs: the full list of organization namesThe active_org is the one already selected — use it without asking. Only surface org selection if active_org is null (nothing marked active).
Check for a .deploio.yaml file in the repo root → set has_deploio_yaml: true/false. If present, note which keys it defines (deployJob, size, env, healthProbe, etc.) so the coordinator can skip asking about those.
{
"nctl_installed": true,
"remote_url": "https://github.com/org/repo.git",
"branch": "main",
"app_type": "rails",
"port": 3000,
"git_auth_type": "https",
"is_monorepo": false,
"active_org": "acme-production",
"available_orgs": ["acme-production", "acme-staging"],
"has_deploio_yaml": false,
"blockers": []
}
blockers is a list of strings, e.g. ["nctl not installed", "no git remote"].
Handle each blocker before proceeding:
nctl not installed:
brew install ninech/tap/nctl # macOS
# Linux: download from https://github.com/ninech/nctl/releases/latest
Re-run Phase 1 after install.
nctl version too old: Deploio requires nctl v1.16.0 or newer. Run brew upgrade ninech/tap/nctl (macOS) or re-download from releases.
No git remote: Deploio always pulls from a git host — it never receives files directly.
git remote add origin https://github.com/<user>/<repo>.git
git push -u origin main
Continue once the user confirms it's pushed.
nctl not authenticated: Ask the user to run nctl auth login (opens browser OAuth) then nctl auth set-project <project>.
app_type unknown: Ask the user what runtime their app uses before proceeding.
GitHub note: GitHub does not support HTTPS deploy tokens for nctl. Use SSH deploy keys for GitHub private repos.
If git_auth_type is ssh:
~/.ssh/id_ed25519ssh-keygen -t ed25519 -f ~/deploio.key -N ''git_ssh_key_path to the executor specIf git_auth_type is https (GitLab or Bitbucket only):
read_repository; pass the token name as git_username and the token value as git_passwordRepository read; use x-auth-token as the username fieldgit_username and git_password to the executor specIf the repo is public, skip this phase.
nctl auth whoami returns the currently active organization (marked *) and the full list.
Normal case — active org exists: Use active_org directly. Do not ask. The plan card will show it for confirmation.
Edge case — no active org: Present the list and ask:
Your account has access to multiple Deploio organizations:
1. acme-production
2. acme-staging
Which organization should this app be deployed to?
(Or run `nctl auth set-org <name>` first to set a default.)
Set selected_org from the user's answer.
Terminology note: Deploio calls the top-level grouping an organization (set with
nctl auth set-org). The app lives within the organization. Do not use the word "project" when referring to this selection — it confuses users who see organization names innctl auth whoamioutput.
The plan card MUST always include Organization, Project, and App — all three, in this order. Never omit Project.
Read the framework file before building the plan card. Once app_type is known from Phase 1, read the corresponding file:
| app_type | File to read |
|---|---|
rails | skills/deploio-deploy/references/RAILS.md |
nodejs | skills/deploio-deploy/references/NODE.md |
django / flask | skills/deploio-deploy/references/PYTHON.md |
php | skills/deploio-deploy/references/PHP.md |
go | skills/deploio-deploy/references/GO.md |
docker | skills/deploio-deploy/references/DOCKER.md |
unknown | Ask the user before proceeding |
Each file contains: default instance size, required env vars, deploy job command, health probe path, and framework-specific warnings. Use these to populate both the plan card and the executor spec.
Here's what I'll set up:
| Setting | Value |
|---|---|
| Organization | <selected_org> |
| Project | <selected_org>-<repo-name> |
| App | <branch> |
| Source | github.com/org/repo · <branch> branch |
| Build | Docker (or: auto-detected buildpack) |
| Size | <from framework file — e.g. mini for Rails, micro for Node.js/Go/Python> |
| Replicas | 1 |
<Framework defaults block — copy "Plan card defaults" section from the framework file>
Deploio will build your app from source in the cloud and give it an HTTPS URL
on deploio.app. By default the URL is publicly accessible — if you'd like to
restrict access with a username and password prompt, just say so and I'll
enable basic auth.
First build and release takes ~2–5 minutes.
Use EnterPlanMode before presenting the plan card so the user can review it fully before committing. Use AskUserQuestion to get confirmation:
question: "Ready to deploy?"
options:
- "Yes, exactly that"
- "Yes, but… (tell me what to adjust)"
- "No, cancel"
Call ExitPlanMode once the user confirms or cancels.
Derive names:
active_org from nctl auth whoami (the *-marked entry)<org>-<repo> (e.g. renuotest-myapp — never just <repo>; nctl errors)<branch> (e.g. main)Available instance sizes (set at creation or anytime after with nctl update app):
| Size | RAM | CPU | CHF/month |
|---|---|---|---|
| micro | 256 MiB | 0.125 | ~8 |
| mini | 512 MiB | 0.25 | ~16 |
| standard-1 | 1 GiB | 0.50 | ~32 |
| standard-2 | 2 GiB | 0.75 | ~58 |
If the user requests basic auth, set basic_auth: true in the executor spec.
If the user says "cancel" or "stop" — do not spawn any agents. Offer to restart later. If the user adjusts a name — update the spec and re-present the card before proceeding.
Once confirmed, use TaskCreate to create a task named "Deploying <app-name>" (status: in_progress) so the user has a visible progress tracker. Then spawn two deploio-cli agents with mode: bypassPermissions and return to the conversation.
Agent 1 — executor
task: deploy
org: <selected_org>
app: <app-name>
git_remote: <remote_url>
branch: <branch>
build: docker | buildpack
port: <port>
size: <size> # default: mini for Rails, micro for everything else
replicas: <replicas or null>
basic_auth: <true or false>
env_vars: <see framework table in references/frameworks.md>
build_env_vars: <see notes below>
deploy_job: <command or null>
deploy_job_timeout: <duration or null>
health_probe_path: <path or null>
git_ssh_key_path: <path or null>
git_username: <token name or null>
git_password: <token value or null>
git_sub_path: <subdir or null>
dockerfile_path: <path or null>
dockerfile_build_context: <path or null>
For Rails apps, if SECRET_KEY_BASE is not already set by the user, the executor must generate one by running openssl rand -hex 64 and using the output as the value. Do not ask the user to provide it.
# 1. Authenticate (skip if already logged in)
nctl auth login
nctl auth set-project <project>
# 2. Create the app
nctl create app <app> \
--git-url=<git_remote> \
--git-revision=<branch> \
[--git-sub-path=<git_sub_path>] # monorepos only
[--git-ssh-private-key-from-file=<git_ssh_key_path>] # SSH auth
[--git-username=<git_username>] # HTTPS auth (GitLab/Bitbucket)
[--git-password=<git_password>] # HTTPS auth (GitLab/Bitbucket)
--port=<port> \
[--size=<size>] # micro|mini|standard-1|standard-2
[--replicas=<n>] # default 1
[--dockerfile] # docker builds only
[--dockerfile-path=<dockerfile_path>] # if Dockerfile is not in repo root
[--dockerfile-build-context=<dockerfile_build_context>] # if build context differs
[--build-env=<KEY=VALUE>] ... # build-time args; repeat or semicolon-separate
--env=<KEY=VALUE> ... # runtime env vars; repeat or semicolon-separate
[--basic-auth] # enable HTTP basic auth
[--deploy-job-command="<deploy_job>"] # if deploy_job is set
[--deploy-job-timeout=<deploy_job_timeout>] # default 5m, range 1–30m
[--deploy-job-retries=<n>] # default 3, max 5
[--health-probe-path=<health_probe_path>] # HTTP path for readiness check
[--health-probe-period-seconds=<n>] # default 10, min 1
[--hosts=<domain1,domain2>] # custom domains at creation time
# 3. Get the live URL and (if basic_auth) credentials
nctl get app <app> --project=<project>
[nctl get app <app> --basic-auth-credentials] # if basic_auth is true
Env var syntax: Multiple env vars can be passed either as repeated flags (--env=KEY1=VAL1 --env=KEY2=VAL2) or semicolon-separated in a single flag (--env='KEY1=VAL1;KEY2=VAL2'). Both forms are accepted. The same syntax applies to --build-env.
On success, report back: { "status": "success", "url": "https://<username>:<password>@<host>", "basic_auth_credentials": { "username": "...", "password": "..." } | null }
On failure, report back: { "status": "failed", "error": "<nctl error output>", "step": "create|auth|url" }
Agent 2 — monitor
task: monitor-logs
app: <app-name>
org: <selected_org>
termination: stop when executor reports success or failure, or after 20 minutes — whichever comes first
The monitor streams nctl logs app <app> --type build -f and relays key lines to the coordinator at regular intervals (every ~30s or on meaningful events). On timeout, emit: "Build is taking longer than expected — check Deploio dashboard for status." and exit.
After spawning both:
"Both agents are running — executor deploying, monitor watching logs. I'll update you as the build and release progresses, or ask me anything while it runs."
You are the coordinator — remain in the conversation. Do not block.
When the monitor sends a progress update: relay it naturally.
"[45s] Build step: installing dependencies..."
When the user asks for a status update: share the latest from the monitor, or check agent status.
Use SendMessage to check in on background agents when the user asks for a status update, rather than waiting passively.
When the executor reports status: success:
Share the URL and any basic auth credentials, then offer structured next steps based on what the user mentioned earlier in the conversation:
Update the deployment task to `completed` using `TaskUpdate`. Then share the result:
Your app is live at [https://<host>](https://<username>:<password>@<host>)
(To rotate the password later: `nctl update app <app> --change-basic-auth-password`)
What's next?
→ Add a database — I can provision PostgreSQL or Redis (deploio-provision)
→ Set a custom domain — point your DNS to Deploio, then add the domain to the app
→ Scale the app — increase replicas or upgrade the instance size
→ Wire up CI/CD — auto-deploy on every git push (deploio-ci-cd)
→ Nothing for now — you're all set!
Custom domain setup (if the user asks):
nctl update app <app> --hosts=yourdomain.comnctl get app <app> --dns — this returns a DNS target and TXT verification recordCNAME pointing to the DNS targetALIAS (preferred) or A record; add the TXT verification recordLead with the option most relevant to the conversation (e.g., if the user mentioned Postgres earlier, put that first).
When the executor reports status: failed:
Update the deployment task to failed using TaskUpdate. Translate the error into plain terms using the table below, then offer a concrete fix:
"The build failed because Rails couldn't find SECRET_KEY_BASE. I'll generate a secure value for you and add it — want me to proceed?"
The framework file (read in Phase 3) contains env vars, deploy job command, health probe path, and build notes. Use it to populate the executor spec — do not re-read it in Phase 4 if already read. If app_type is unknown, ask the user before spawning the executor.
If the user mentions PostgreSQL, MySQL, Redis, Sidekiq, or file storage:
"Deploio can provision a managed PostgreSQL database, Redis-compatible KVS (for Sidekiq), and S3-compatible object storage. I can set those up once the app is live using deploio-provision."
Don't block the deployment for services — unless a required env var is missing (e.g. REDIS_URL for Sidekiq), in which case ask before proceeding.
If the framework is Rails and Gemfile contains sidekiq, or the user mentions background workers, note that a worker job can be added at creation time with a dedicated size (defaults to micro).
Scheduled jobs (Deploio's term — do not call them "cron jobs") can be added with a name, schedule string, and command. Both worker jobs and scheduled jobs are configured on the same app resource and run in separate containers with their own resource allocation.
Only mention .deploio.yaml if the user seems like a team lead, is setting up a shared project, or explicitly asks about config management. Do not offer it to users doing a quick first deploy.
If it comes up naturally, explain it briefly:
"If you're working in a team, a
.deploio.yamlin your repo can track your deploy config in git — things like size, replicas, and deploy job settings — so the team can see and review changes. Want me to create one?"
Only create it if the user says yes. See references/RAILS.md (or the relevant framework file) for a template.
If a .deploio.yaml already exists (detected in Phase 1), read it to pre-fill the plan card values and skip any settings already defined there.
PORT, DEPLOIO_APP_NAME, DEPLOIO_PROJECT_NAME, DEPLOIO_RELEASE_NAME/workspace/tmp); Dockerfile apps control their own filesystem permissionsweb key in a Procfile is respected; other process types are ignoredRead skills/shared/troubleshooting.md for a full list of problems and fixes. Key patterns to diagnose from executor error output:
KeyError or NameError → missing env var → add it and retryPG::ConnectionBad → DATABASE_URL not set → re-run with env varreferences/frameworks.mdGemfile.lock or package-lock.json not committedIf the first deploy fails (build or release), offer to retry via deploio-manage: --retry-build (build failure) or --retry-release (release/migration failure).
Provides UI/UX resources: 50+ styles, color palettes, font pairings, guidelines, charts for web/mobile across React, Next.js, Vue, Svelte, Tailwind, React Native, Flutter. Aids planning, building, reviewing interfaces.
Searches MemPalace before answering questions about past work, people, projects, or prior decisions. Returns verbatim stored content instead of guessing from model memory.
npx claudepluginhub renuo/deploio-claude-plugin --plugin deploio