> ## Documentation Index
> Fetch the complete documentation index at: https://docs.instapods.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Exec & SSH

> Run commands and manage SSH access from the CLI.

## Execute Commands

Run a command inside a pod via the API (no SSH key needed):

```bash theme={null}
instapods exec my-app -- ls -la /home/instapod/app
instapods exec my-app -- npm install
instapods exec my-app -- python manage.py migrate
```

The `--` separator is required to prevent flag parsing conflicts. Commands run as the `instapod` user, starting in that user's home directory (`/home/instapod`).

Everything after `--` is an **argv vector**, not a shell line: each argument is passed through verbatim, so shell syntax (pipes, redirects, `&&`, globs) needs an explicit shell.

```bash theme={null}
# Wrong -- the pod looks for a program literally named "npm ci"
instapods exec my-app "npm ci"

# Right -- as an argv vector
instapods exec my-app -- npm ci

# Right -- as a shell line
instapods exec my-app -- sh -c "cd /home/instapod/app && npm ci"
```

If a command fails, both stdout and stderr are displayed so you can see the actual error message. `instapods exec` also exits with the **command's own exit status**, so `127` (command not found) and `126` (not executable) stay distinguishable from an ordinary failure.

### Flags

| Flag | What it does |
| - | - |
| `-w`, `--workdir DIR` (alias `--cwd`) | Run the command in `DIR` instead of `/home/instapod`. A **relative** path resolves against the pod's app root, so `--workdir .` is the app root — the same rule the MCP `exec_command` tool applies to its `cwd`, so one form works on both surfaces. |
| `--json` | Print `{"output": ..., "exit_code": ...}` instead of raw output |

Flags go **before** `--`:

```bash theme={null}
instapods exec my-app --workdir . -- npm install          # . is the app root
instapods exec my-app --workdir /home/instapod/app -- npm install
instapods exec my-app --json -- node --version
```

A single exec call is capped at 60 seconds. Longer work (a full install or build) belongs in `instapods deploy`, which runs it server-side without that ceiling.

## Inspect the Runtime

Before writing code that depends on what is inside a pod, ask:

```bash theme={null}
instapods runtime my-app
```

```
my-app (nodejs · launch · running)

OS                Ubuntu 24.04.4 LTS
User              instapod (passwordless sudo — install with: sudo apt-get install -y PKG)
CPU / memory      1 vCPU · 512 MB (70.9 MB used)
App root          /home/instapod/app
Disk              4.3 GB free of 5.2 GB

Runtimes
  node            v20.20.2
  npm             10.8.2
  python3         Python 3.12.3
  not installed   composer, go, php, pnpm, ruby, yarn

Tools
  present         curl, git, openssl, tar, unzip, wget
  missing         chromium, ffmpeg, imagemagick, jq, make, rsync, sqlite3
                  a missing tool is installable — apt-get works in this pod

Limits
  Request body      100 MB per API request (files sync chunks larger uploads automatically)
  Exec timeout      60s per exec call
```

Everything is read from the running pod, not inferred from the preset — so a binary listed as missing really is missing. Pods have passwordless `sudo`, so a missing package is one `sudo apt-get install -y <pkg>` away.

Add `--json` for the same report as structured data (versions, byte counts, and the request-size limits), which is the shape to parse from a script or an agent.

### Examples

```bash theme={null}
# Check Node.js version
instapods exec my-app -- node --version

# Install Python dependencies
instapods exec my-app -- /home/instapod/app/venv/bin/pip install -r requirements.txt

# Run a database migration
instapods exec my-app -- php artisan migrate

# Check disk usage
instapods exec my-app -- df -h

# Run a shell command with pipes
instapods exec my-app -- bash -c "cat /etc/os-release | head -5"
```

## SSH Keys

### List SSH Keys

List SSH keys on a specific pod:

```bash theme={null}
instapods ssh-keys list my-app
```

List your account-level SSH keys (no pod name):

```bash theme={null}
instapods ssh-keys list
```

### Add an SSH Key

Push your local SSH public key to a pod:

```bash theme={null}
# Use default key (~/.ssh/id_ed25519.pub or ~/.ssh/id_rsa.pub)
instapods ssh-keys add my-app

# Use a specific key file
instapods ssh-keys add my-app --key ~/.ssh/custom_key.pub
```

The CLI auto-detects keys in this priority order: `id_ed25519.pub`, `id_rsa.pub`, `id_ecdsa.pub`.

After adding a key, the CLI shows the SSH command you can use to connect:

```
Adding key: ~/.ssh/id_rsa.pub
Key added to pod my-app

Connect with:
  ssh instapod@my-app.nbg1-1.instapods.app -p 2237
```

### Remove an SSH Key

Remove an account-level SSH key by ID:

```bash theme={null}
instapods ssh-keys remove KEY_ID
```

## SSH Connection

Once your key is added, connect via SSH:

```bash theme={null}
instapods ssh my-app
```

Or manually using the pod's SSH details:

```bash theme={null}
ssh instapod@nbg1-1.instapods.app -p PORT
```

Find the SSH port via:

```bash theme={null}
instapods pods get my-app
```

### SSH Security

InstaPods uses **Trust On First Use (TOFU)** for SSH host key verification:

* On first connection, the host key is automatically accepted and saved
* On subsequent connections, the key is verified against the saved copy
* If the key changes (which shouldn't happen for a running pod), you'll get a warning

<Note>
  When you delete a pod, the CLI automatically cleans up the old host key from your `~/.ssh/known_hosts` file to prevent conflicts. Deleted pod SSH ports are reserved for 7 days before reuse.
</Note>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.