# Zeabur Server Ssh

> Use when debugging services on a user's dedicated server via SSH. Use when needing to run a command on the server, inspect pods, check container logs, view k8s resources, or run kubectl commands. Use when "service exec" is insufficient and you need server-level access. Use when user says "check my server", "run X on my server", "debug pod", "kubectl", "SSH into server", "check k8s", or "inspect cluster".

- Skill: `zeabur/zeabur-server-ssh` (Agent Skill)
- Install (CLI): `npx skillmds@latest add zeabur/zeabur-server-ssh`
- Raw SKILL.md: https://api.skillmd.com/api/skills/zeabur/zeabur-server-ssh/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: zeabur (https://skillmd.com/u/zeabur)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/zeabur/zeabur-server-ssh

---


# Zeabur Server SSH + kubectl

> **Always use `npx zeabur@latest` to invoke Zeabur CLI.** Never use `zeabur` directly or any other installation method.

Run commands on a user's dedicated server, and use kubectl to debug Kubernetes
workloads on servers that run ZeaburOS.

## Check which kind of server you are on before using kubectl

- **ZeaburOS servers** run k3s with kubectl pre-installed. Everything in this
  skill applies.
- **Ubuntu** — a rented server that is not running ZeaburOS. It has
  **no k3s and no kubectl**; every `kubectl` command below fails with
  `command not found`. `server exec` still works for ordinary shell commands.

**Renting provisions Ubuntu only**, so never assume kubectl exists.

**Read the machine's kind from the CLI — do not SSH in to work it out:**

```bash
npx zeabur@latest server get <server-id> -i=false --json | jq -r .os
```

`ZeaburOS` or `Ubuntu`. `server list` reports the same thing for every server at
once, as an `OS` column and an `os` field.

> Needs CLI **0.21.0 or newer**. If `os` comes back `null`, the CLI is older —
> either let `npx zeabur@latest` fetch the current version, or fall back to the
> API (see the `zeabur-server-rent` skill for the token setup that defines
> `$ZAPI_CFG`):
>
> ```bash
> curl -sS --max-time 30 -K "$ZAPI_CFG" https://api.zeabur.com/graphql \
>   -H "Content-Type: application/json" \
>   -d '{"query":"query($id: ObjectID!) { server(_id: $id) { hasK3s } }","variables":{"id":"<server-id>"}}'
> ```
>
> `hasK3s` is three-state and the third state is a trap: `true` is ZeaburOS,
> `false` is Ubuntu only, and `null` is a server predating the field — which
> **does** run ZeaburOS. So the test is `hasK3s === false`, never `!hasK3s`; the
> latter sweeps in every older server and wrongly reports it as a plain VPS.

If you did not check first and a `kubectl` command fails with `command not
found`, that is the same signal — tell the user the server is a plain Ubuntu VPS
instead of retrying the command. Installing ZeaburOS is what adds kubectl; the
`zeabur-server-rent` skill covers how.

**One exception.** All of the above answers *what kind of machine this is*. If
the user asks specifically whether some binary is present, check the machine —
a ZeaburOS server always has `kubectl`, but a plain Ubuntu VPS may have had it
installed by its owner, and the OS field cannot tell you that.

## Run a command: `server exec` (recommended)

Run a command on the server in one step. The CLI fetches the credentials and opens
the connection internally, so **you never handle the password** — passwords with
special characters just work, and no `ssh2`/`sshpass` is needed.

```bash
npx zeabur@latest server exec --id <server-id> -- <command>
```

Examples:

```bash
# Single command
npx zeabur@latest server exec --id <server-id> -- sudo kubectl get pods -A -o wide

# Compound command — quote it as ONE argument so && / | stay intact
npx zeabur@latest server exec --id <server-id> -- 'echo "=== PODS ===" && sudo kubectl get pods -A && echo "=== EVENTS ===" && sudo kubectl get events -A --sort-by=.lastTimestamp | tail -20'
```

Notes:

- Everything after `--` is the remote command, joined like `ssh host <command>`.
  Quote a compound command (with `&&` / `|` / redirects) as a **single argument**.
- stdout/stderr stream live; the remote command's **exit code is propagated**
  (a pipeline reports only its **last** stage's status — `… | tail` hides an
  upstream failure, so don't rely on the exit code across a pipe).
- If you don't know the server ID, list servers first:
  ```bash
  npx zeabur@latest server list -i=false
  ```
  (or use the `zeabur-server-list` skill).

## Common kubectl Commands

> **ZeaburOS servers only.** On a plain Ubuntu VPS these all fail with
> `kubectl: command not found` — see the section above.

Pass any of these as the command to `server exec`. **Always use `sudo kubectl`** —
the SSH user may not have direct access to the k3s kubeconfig. Any command with a
pipe (`|`) or `&&` must be **quoted as a single argument**, or the local shell
splits it and runs part locally — e.g.
`server exec --id <id> -- 'sudo kubectl top pods -A --sort-by=memory | head -20'`.

| Task | Command |
|------|---------|
| List all pods | `sudo kubectl get pods -A -o wide` |
| Problem pods only | `sudo kubectl get pods -A --field-selector=status.phase!=Running,status.phase!=Succeeded` |
| Pod logs | `sudo kubectl logs <pod-name> -n <namespace> --tail=100` |
| Exec into container | `sudo kubectl exec <pod-name> -n <namespace> -- <command>` |
| Node resources | `sudo kubectl top nodes` |
| Pod resources | `sudo kubectl top pods -A --sort-by=memory \| head -20` |
| Describe pod | `sudo kubectl describe pod <pod-name> -n <namespace>` |
| Recent events | `sudo kubectl get events -A --sort-by=.lastTimestamp \| tail -30` |
| Restart deployment | `sudo kubectl rollout restart deployment/<name> -n <namespace>` |

## Fallback: manual SSH (only if `server exec` is unavailable)

Use this **only** if `server exec` isn't available (e.g. an older CLI). Otherwise
prefer `server exec` above — this path is fragile with special-character passwords.

### Step 1: Get SSH credentials

```bash
npx zeabur@latest server ssh-info --id <server-id> -i=false
```

Output is JSON: `{"ip":"1.2.3.4","port":22,"username":"root","password":"xxx"}`

### Step 2: Connect

Use the Node.js `ssh2` method by default; use `sshpass` only when its availability
is already known. Do NOT run `which sshpass` to check — it wastes a step where it's
never installed.

```bash
# sshpass (only if already known to be available)
sshpass -p '<password>' ssh -o StrictHostKeyChecking=no -p <port> <username>@<ip> sudo kubectl get pods -A
```

```bash
# Node.js ssh2 (the Zeabur agent sandbox has it pre-installed)
NODE_PATH=$([ -d /root/.global/node_modules ] && echo /root/.global/node_modules || echo /home/vercel-sandbox/.global/node_modules) node -e "
const {Client} = require('ssh2');
const c = new Client();
c.on('ready', () => {
  c.exec('<command>', (err, stream) => {
    if (err) { console.error(err); process.exit(1); }
    let out = '', errOut = '';
    stream.on('data', d => out += d);
    stream.stderr.on('data', d => errOut += d);
    stream.on('close', code => {
      if (out) console.log(out);
      if (errOut) console.error(errOut);
      c.end();
      process.exit(code);
    });
  });
}).connect({host:'<ip>', port:<port>, username:'<username>', password:'<password>'});
"
```

## Tips

- **Combine commands**: batch related checks with `&&` in a single `server exec`
  call to reduce round trips.
- **Use `-o wide`**: adds node name and IP to pod listings, useful for scheduling issues.
- **Namespace matters**: Zeabur services usually run in non-default namespaces. Use
  `-A` (all namespaces) first to locate the right one, then scope with `-n <namespace>`.
- **Read project docs first**: if a fix attempt fails, exec into the container and
  check README/config before blindly checking metrics: `sudo kubectl exec <pod> -n <ns> -- cat /app/README.md`
- To find server IDs, use the `zeabur-server-list` skill. For simpler container
  commands that don't need server-level access, use the `zeabur-service-exec` skill.

