Script
A command you wrote, run by your worker. The most useful source in the list.
View as MarkdownRuns a command on your worker's host and stores whatever it writes to a path we give it.
Local only, permanently. We do not execute customer commands in our cloud, and the workflow is not registered there, so there is no configuration that could route one to us.
That constraint is what makes it useful. It is the escape hatch for every source we have no connector for, running on your hardware, with no work from us and no new trust granted to anyone.
The contract
One rule: write your backup to $SAVED_OUTPUT.
#!/usr/bin/env bash
set -euo pipefail
mongodump --archive --db=app > "$SAVED_OUTPUT"$SAVED_OUTPUT | A path the worker creates and passes in. Write the artifact here |
| stdout and stderr | Captured as a log, kept for diagnostics, and never stored as data |
| Exit code | Zero means success. Anything else fails the run |
Piping to stdout does not work. mongodump --archive on its own writes to stdout, and
the run fails with EmptyOutput: the script exited zero without writing to the path.
That is deliberate. Storing stdout as the artifact would make every stray echo corrupt
the backup.
Fields
| Key | Required | Notes |
|---|---|---|
path | Yes | Must be executable, unless interpreter is set |
args | No | A list, one entry per argument |
interpreter | No | Runs <interpreter> <path> <args...> |
working_dir | No | Directory to run in |
backups:
"<backup-uuid>":
source_type: script
source:
path: /opt/saved/dump-everything.sh
working_dir: /opt/saved
args: ["--db", "app"]interpreter is how you run a script that is not executable, or run a Python file without a
shebang:
source:
path: /opt/saved/export.py
interpreter: /usr/bin/python3Without an interpreter the file must be executable, and a non-executable path is refused with
a message saying to chmod +x it or set one.
Patterns
Anything with a CLI is a backup source.
# MongoDB
mongodump --archive --db=app > "$SAVED_OUTPUT"
# An application directory
tar -czf "$SAVED_OUTPUT" -C /srv/app data config
# Two things at once
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
pg_dump -Fc app > "$tmp/app.dump"
cp /etc/app/config.yaml "$tmp/config.yaml"
tar -cf "$SAVED_OUTPUT" -C "$tmp" .
# A vendor CLI with its own export
vendor-cli export --format=json --out "$SAVED_OUTPUT"The third pattern is the one worth internalising: one run makes one artifact, so if you need several things together, tar them.
Writing a script that fails properly
The most common bug is a script that succeeds while producing nothing useful.
#!/usr/bin/env bash
set -euo pipefail # fail on error, unset variable, or a broken pipeset -euo pipefail is doing real work here. Without pipefail, pg_dump | gzip > out exits
zero when pg_dump fails, because gzip succeeded, and you get a valid gzip of nothing.
Three checks worth adding:
# Verify the thing you depend on exists
command -v mongodump >/dev/null || { echo "mongodump not installed" >&2; exit 1; }
# Verify the output is not suspiciously small
mongodump --archive --db=app > "$SAVED_OUTPUT"
[ "$(stat -f%z "$SAVED_OUTPUT" 2>/dev/null || stat -c%s "$SAVED_OUTPUT")" -gt 1024 ] \
|| { echo "dump is implausibly small" >&2; exit 1; }The worker catches the zero-byte case for you. It cannot catch "the dump ran against an empty database because the connection string was wrong", and your script can.
Failure and diagnostics
| Situation | Result |
|---|---|
| Exit code zero, output written | Success |
| Exit code zero, nothing written | EmptyOutput, non-retryable |
| Non-zero exit | The run fails, with your stdout and stderr as the message |
| Path missing, not executable, or a directory | InvalidSource, non-retryable |
Output is truncated to 2000 characters in the run record, so print a useful message rather than a stack trace of everything. Progress is reported as a byte count while the script runs, so a long-running script is visibly alive.
Environment and permissions
The script inherits the worker's environment plus SAVED_OUTPUT, and runs as the worker's
user.
That user needs to be able to do whatever the script does. Most "works when I run it, fails
under the worker" reports are a script that depends on a shell profile, a PATH entry, or a
~/.pgpass that belongs to your login rather than the service account.
Test it the way the worker will run it:
sudo -u saved SAVED_OUTPUT=/tmp/test.out /opt/saved/dump-everything.sh
ls -l /tmp/test.outSecrets belong in the script's own environment or a file it reads, not in args, which are
visible in the process list.
Disk
The script writes to the worker's staging directory, and the artifact is then encrypted into
a second file before either is discarded. Budget roughly twice what your script produces,
per concurrent run. See local_temp_path.