# Commands

A command job runs a program on the cronpad server when it fires. It's available only on self-hosted servers (`mode = "selfhosted"`, the default) and can be turned off or restricted to an allowlist. cronpad captures the exit code and output, kills the whole process group on timeout, and retries based on exit codes you choose.

:::caution
Commands run with the permissions of the cronpad process, inside its container if you use Docker. Anyone who can create jobs in a workspace can run commands. Restrict them with `sinks.command.allowed_commands`, or disable them with `sinks.command.enabled = false`.
:::

## Configuration

```json
{
  "kind": "command",
  "config": {
    "command": "/usr/local/bin/backup.sh",
    "args": ["--full", "--target", "s3://backups/{{run.scheduled_at}}"],
    "env": {"AWS_SECRET_ACCESS_KEY": "{{secret.AWS_SECRET}}"},
    "workdir": "/srv",
    "shell": false,
    "success_exit_codes": [0],
    "retry_exit_codes": [75]
  }
}
```

| Field | Default | Description |
|---|---|---|
| `command` | required | Path of the executable, or a command line when `shell` is `true`. |
| `args` | `[]` | Arguments, passed directly (no shell quoting issues). |
| `env` | `{}` | Extra environment variables. Values may use [templates](index.md#templates). |
| `workdir` | process working dir | Working directory. |
| `shell` | `false` | Run `command` through `sinks.command.shell` (default `/bin/sh -c`), allowing pipes and redirects. |
| `success_exit_codes` | `[0]` | Exit codes that mean success. |
| `retry_exit_codes` | `[]` | Exit codes that are retryable. Any other non-success code is fatal. Timeouts are always retryable. |

The job's `timeout` (default `60s`) applies to each attempt. On timeout, cronpad kills the command's entire process group, so child processes don't linger.

## Environment

The command does **not** inherit cronpad's environment (which contains its own secrets). It receives:

- `PATH`, `HOME`, `LANG` and any variables listed in `sinks.command.env_passthrough`;
- `CRONPAD_RUN_ID`, `CRONPAD_JOB_ID`, `CRONPAD_SCHEDULED_AT`, `CRONPAD_ATTEMPT`;
- the job's `env`.

## Output

Standard output and standard error are captured together, up to 64 KiB (`sinks.command.max_output_bytes`), and stored as the attempt's response body.

## Restricting commands

```toml
[sinks.command]
enabled = true
allowed_commands = ["/usr/local/bin/backup.sh", "/usr/local/bin/report"]
```

When `allowed_commands` is non-empty, jobs can only use those executables and `shell = true` is rejected.

## Running commands in Docker

cronpad publishes one image, built on Alpine (`/bin/sh`, `curl`, `ca-certificates`).

Commands run inside the cronpad container. Build your own image `FROM` the cronpad image that adds your scripts and the tools they need, or run cronpad as a binary on the host.