Files and folders
Your own filesystem, taken by your own worker.
View as MarkdownTwo source types read the disk of the machine your worker runs on.
| Type | Takes | Produces |
|---|---|---|
file | One regular file | The file, under its own name |
folder | A directory tree | One zip |
Local only, permanently. There is nothing for our cloud to connect to: your filesystem does not exist there. The workflows are not registered on our side, so no configuration can route one to us.
file
| Key | Required | Notes |
|---|---|---|
path | Yes | Absolute path to a regular file |
follow_symlinks | No | true to back up a symlink's target |
backups:
"<backup-uuid>":
source_type: file
source:
path: /var/lib/app/state.dbThe artifact keeps the file's base name. With follow_symlinks set, that is the target's
name, not the link's: a link current.db pointing at db-2026-08.sqlite produces an artifact
called db-2026-08.sqlite.
Anything that is not a regular file is refused with an explanation rather than silently producing something odd:
| Path is | Result |
|---|---|
A symlink, without follow_symlinks | Refused. Set the flag to back up its target |
| A directory | Refused, telling you to use folder |
| A socket, device or FIFO | Refused. There is nothing to copy |
A file backup copies the bytes as they are at that moment. If the file is being written
while the copy runs, you get a torn read. For a SQLite database, back it up with a
script source running sqlite3 db ".backup" rather than
copying the file underneath a live writer.
folder
| Key | Required | Notes |
|---|---|---|
path | Yes | Absolute path to a directory |
include | No | A list of globs. Empty means every regular file |
exclude | No | A list of globs. Applied before include |
follow_symlinks | No | true resolves links instead of storing them |
backups:
"<backup-uuid>":
source_type: folder
source:
path: /srv/uploads
exclude: ["*.tmp", "node_modules", ".git", "*.log"]The artifact is <folder-name>.zip.
Patterns
A pattern matches if it matches any of these:
- the full path relative to the root (
docs/notes/todo.md) - the base name (
todo.md) - any single path segment (
notes)
That is why node_modules excludes the directory wherever it appears, at any depth, without
needing **/node_modules/**.
| Pattern | Matches |
|---|---|
*.tmp | Any file ending .tmp, at any depth |
node_modules | Any directory or file with that exact name |
cache/* | Direct children of a top-level cache |
Exclude is evaluated first, and excluding a directory skips its whole subtree, so an
include cannot pull a file back out of an excluded directory. Order the two accordingly.
include filters regular files only. Directory entries and symlinks are archived
whatever it says, so a zip built with include: ["*.pdf"] still contains the tree's
directory structure and its links. exclude is the one that applies to everything, and it
is the right tool for leaving something out.
Excluding aggressively is worth the effort. Build output, dependency directories and logs are usually the majority of the bytes and none of the value.
Symlinks
follow_symlinks | Behaviour |
|---|---|
| Unset | Links are stored as links, recording their target text |
true | Links are resolved and their targets archived in place |
With following on, each resolved target is archived once, so a link cycle cannot produce an infinite archive. Two links to the same file mean one copy, under the first name reached.
What survives the round trip
| Preserved | Not preserved |
|---|---|
| Paths and directory structure | Ownership (uid/gid) |
| File contents | Unix permission bits |
| Symlinks, as links or as targets | Modification and access times |
| Extended attributes, ACLs, SELinux labels | |
| Hard link relationships |
If ownership, permissions or timestamps matter, do not use folder. Use a
script source running tar with the flags you need:
tar --numeric-owner --acls --xattrs -czf "$SAVED_OUTPUT" -C /srv app. Tar preserves what
zip cannot, and you keep control of exactly which metadata is captured.
Entries are stored without zip compression, because the encryption step compresses
anyway and doing it twice costs CPU for nothing. The .zip you download is a container, not
a compressed archive.
Consistency
Neither type snapshots. A tree walked over ten minutes reflects the filesystem at ten different moments, and a file changed halfway through is captured mid-change.
For anything actively written, back up the application's notion of a consistent copy rather than the filesystem's:
- A database: use its own source type, or its dump tool via
script. - An LVM or ZFS volume: snapshot it in a
script, back up the snapshot, release it. - An upload directory that only ever appends:
folderis fine as-is.
Sizing
The zip is the sum of the files plus per-entry overhead, uncompressed. A tree of a million small files produces a large archive and a slow walk; progress is reported per 200 files so you can see it moving.
Budget roughly twice the tree's size on the worker's staging disk: the zip and its encrypted copy exist at the same time.
Restoring
sctl restore folder <artifact-id> --path ./uploads.zip
unzip ./uploads.zip -d /srv/uploadsCheck ownership, permissions and timestamps afterwards. None of them are in the archive.