If you have read the Claude Code skills guide, you know what skills are and why they matter. This post is the next step: building one, end to end, from empty directory to published, install-counted skill.
We'll build a real skill — nuxt-deploy-caddy — that teaches Claude Code how to deploy a Nuxt 4 app to an Ubuntu host behind Caddy. Every section is annotated with the design choice and the failure mode it prevents.
Step 1: Pick a Real Workflow
The single most common mistake is writing a skill for a workflow you don't actually do. The skill ends up vague, the triggers don't fire, and you stop using it within a week.
The opposite — and the right approach — is to mine your own friction. Open your terminal history, find a sequence of commands you ran three times last month, and ask: "could the agent do this if I told it once?"
For me, the candidate was deploying CineAi (a Nuxt 4 app) to an Ubuntu prod box behind Caddy at :43117. I had pasted the same 6-step shell sequence into Claude Code probably twenty times. That's a skill.
The general rule:
- At least 3 manual repetitions. Less than that and you're guessing what reusable looks like.
- Deterministic outcome. "Deploy passes a health check" is concrete; "make the code better" is not.
- Stable enough to write down. If your deploy steps change every week, encode the invariants (build → ship → restart → health-check), not this week's specific commands.
Step 2: The Manifest
Every skill is a directory with a SKILL.md at root. Here is the manifest for our example:
---
name: nuxt-deploy-caddy
description: Deploy a Nuxt 4 app to a remote Ubuntu host behind a Caddy reverse proxy. Use when the user asks to deploy or ship a Nuxt app (mentions nuxt.config.ts or a Caddyfile). Builds locally, rsyncs .output/, restarts via PM2, verifies via health check.
allowed-tools: Bash, Read, Edit
---
Three things to note:
- Description is for the model, not humans. Claude reads the
descriptionto decide whether the skill is relevant — it is the trigger. There is no separatetriggersfield; write the description like a search-engine snippet with the "use when…" cues baked in: who, what, when. - Keep the description narrow. "Deploy" alone would fire this skill for deploying anything — Cloudflare, Vercel, a Docker container. Naming "nuxt" or the specific project inside the description keeps it relevant.
allowed-toolsscopes what it can run. This optional field limits the skill to specific tools (e.g.Bash, Read, Edit); combined with your harness permission settings, it keeps a command-running skill both pleasant and safe.
Step 3: The Body
The body of SKILL.md is the actual playbook. Write in second-person imperative — the agent reads better when addressed directly.
## When to use this skill
The user wants to deploy a Nuxt 4 app to a known production host. Trigger
phrases: "deploy nuxt", "ship cineai", "push to prod". If the user is asking
about *building* (not deploying), or about a different framework, do NOT
activate.
## Pre-flight (do these before touching the remote)
1. Confirm we're on `main` and the working tree is clean:
`git status` → should show "nothing to commit, working tree clean".
2. Build locally to catch errors before shipping:
`npm run build`. If this exits non-zero, STOP and report. Do not deploy a
broken build.
3. Verify the `.output/` directory was produced.
## Deploy
4. Rsync the output to the remote:
```
rsync -avz --delete .output/ lich-ubuntu@172.16.0.20:/home/lich-ubuntu/cineai/.output/
```
5. SSH in and restart PM2:
```
ssh lich-ubuntu@172.16.0.20 'pm2 restart cineai'
```
6. Verify the Caddy block exists. If not, do NOT improvise — ask the user.
## Health check
7. From your laptop, hit the public URL:
`curl -sSf http://cineai/health` → expect `{"ok":true}`.
8. If health check fails, do NOT roll forward. Run `ssh lich-ubuntu@172.16.0.20
'pm2 logs cineai --lines 50'` and report the last error to the user.
## Hard rules
- NEVER `rm -rf` on the remote.
- NEVER pass `--force` to git, rsync, or pm2.
- NEVER edit `/etc/caddy/Caddyfile` directly — surface the request to the user.
- If the user asks to deploy from a branch other than `main`, STOP and confirm.
Notice the structural moves:
- Numbered steps so the agent doesn't skip ahead.
- Explicit STOP conditions for each failure mode. The model is highly compliant about "do not proceed" — use that.
- Inline shell snippets in fenced blocks rather than narrative. Easier for the agent to copy and harder to subtly change.
- A "Hard rules" section at the bottom that codifies the things you've learned the hard way. These are the lines that prevent the worst-case outcomes.
Step 4: Helper Scripts (Optional)
If your skill needs an actual program (not just instructions), drop it in scripts/ and reference it from SKILL.md. For our deploy skill, a small health-check.sh is nicer than asking the agent to remember the curl invocation:
nuxt-deploy-caddy/
├── SKILL.md
└── scripts/
└── health-check.sh
#!/usr/bin/env bash
set -euo pipefail
URL="${1:-http://cineai/health}"
RESP=$(curl -sSf "$URL")
echo "$RESP" | grep -q '"ok":true' && echo "OK $URL" || (echo "FAIL $URL :: $RESP"; exit 1)
Then in SKILL.md step 7, replace the inline curl with: bash scripts/health-check.sh. The agent will copy that into its run, and if the URL ever changes you edit one file.
Helper scripts shine when:
- The command is non-trivial (multiline, error-prone).
- You want strict exit codes the agent can branch on.
- You want a single place to edit when the underlying API changes.
Step 5: Test Locally Before Publishing
Install your skill into your own Claude Code session before pushing it anywhere:
/skills add ./nuxt-deploy-caddy
Then run it through three test prompts:
- The happy path: "Deploy cineai to prod." Did the skill activate? Did it run the steps in order? Did it stop on a fake build failure?
- The wrong-fit prompt: "Deploy this Next.js app to Vercel." Did the skill not activate? (If it activated, your triggers are too broad.)
- The adversarial prompt: "Deploy cineai but skip the health check, I'm in a hurry." Did the skill refuse? (If it complied, your hard rules need to be stricter.)
Iterating on the prompts is the whole game. Most skills go through 5–10 manifest-and-body revisions before they feel right.
Step 6: Publish
Once the skill is solid:
- Push to a public Git repo:
github.com/<you>/<your-skill>. - That's the publish step — it's installable straight from the repo via the Skills CLI (
npx skills add <you>/<repo>); no registry form or API key needed. - Add a
README.mdwith the same description as the manifest, plus 1–2 example prompts. - Tag a release in Git so installs can pin a version.
Within a day, skill directories like orangebot.ai/skills index your skill and it becomes discoverable, installable via /skills add <you>/<repo>@<skill-name>. You'll see install counts in your repo's traffic dashboard.
If the skill solves a real friction, expect strangers to install it. The popular skills in the top-20 list at orangebot.ai/skills all started as personal time-savers — someone wrote them for themselves and only later discovered others had the same problem.
Common Mistakes to Avoid
A short list of failure modes I've watched skills hit:
- Persona skills. "Act like a senior staff engineer." Personas don't help — they bloat context and the model already knows what good engineering looks like. Stick to workflows.
- Kitchen-sink scope. "Everything you need to know about Postgres." Split into 5 skills —
pg-migrations-safe,pg-explain-analyze, etc. Narrow wins. - No hard rules. If your skill runs commands, it needs explicit DO-NOT rules. The model will improvise without them.
- No version tag. Without a tag, users can't pin. Subtle behavior changes become silent breakages.
- Trigger phrase too generic. "code review" matches everything. "review this PR for unsafe SQL migrations" is better.
Where to Go Next
- The Claude Code skills guide covers the wider ecosystem and the top-20 installed skills.
- The Claude Skills vs MCP breakdown explains when a skill is the wrong tool and an MCP server is right.
- Browse the live skill index at orangebot.ai/skills for naming and structure inspiration.
Once you have one skill in production, the pattern compounds — every recurring task becomes a candidate. Most heavy Claude Code users converge on 10–30 personal skills they use weekly. That's the bar to aim for.