ssh
Runs commands and writes files on a remote host. Shells out to the system ssh binary with -o BatchMode=yes -o StrictHostKeyChecking=accept-new, so authentication is whatever your ~/.ssh/config and ssh-agent provide. Passwords are not supported — keys only.
The provider takes no configuration block.
Kinds
ssh_exec
Runs a command on a remote host. Stratum re-runs the command whenever any attribute changes (typically command).
resource "ssh_exec" "uptime" {
host = host.prod.addr
command = "uptime"
}
resource "ssh_exec" "bootstrap" {
host = host.prod.addr
command = "mkdir -p /opt/stratum"
on_destroy = "rm -rf /opt/stratum"
}
| attr | required | type | default | description |
|---|---|---|---|---|
host | yes | string | — | SSH target in user@host form. Passed verbatim to ssh. |
command | yes | string | — | Shell command to run on the remote host. |
env | no | map | none | Each entry becomes export KEY=VALUE; prepended to command. Values are shell-quoted. A secret.<name>.value ref is supported here (state stores a redaction marker, same as docker_container.env). Keys are sorted for deterministic command output. |
on_destroy | no | string | none | Command to run when this resource is deleted from config. |
Stored state adds stdout, stderr, and exit_code from the last run.
The effective remote command is export K1=V1; export K2=V2; ...; <command> — a single shell invocation, not a separate environment. The env map is the right place for short, sensitive values (a GitHub PAT, a deploy token) that flow into a one-shot command without landing in a file. For values that need to persist on the host, use system_secret_file.
If the remote command exits non-zero, apply fails with the captured stderr.
When the resource is removed from config and on_destroy is set, the command is executed; otherwise delete is a no-op (with a log line).
Drift detection: read always returns Observed::Unknown("ssh_exec has no readable identity on the host"). ssh_exec resources show up in unreadable counts on stratum plan --refresh and after every stratum apply -y. That's intentional — a fire-and-forget shell command has no canonical "current state" to read back.
ssh_file
Writes a file to a remote path. The file is re-uploaded whenever its content changes, detected via sha256 of the prior state. Deletion runs rm -f on the recorded path.
resource "ssh_file" "motd" {
host = host.prod.addr
path = "/etc/motd"
content = "Managed by stratum.\n"
mode = "0644"
}
| attr | required | type | default | description |
|---|---|---|---|---|
host | yes | string | — | SSH target in user@host form. |
path | yes | string | — | Absolute destination path on the remote host. |
content | yes | string | — | File contents, written verbatim. |
mode | no | string | 0644 | File mode, passed to install -m. |
Upload uses install -m <mode> /dev/stdin <path> with the content streamed via stdin. Binary content is not officially supported (the value comes through the parser as a UTF-8 string).
Stored state: { host, path, mode, sha256 }. The hash is what stratum compares on the next plan to decide whether to re-upload.
For richer file management (owner, group, auto-created parent dirs, content_file = "..." inlining), use system_file instead.
Drift detection: read runs a single-roundtrip probe: if [ -e <path> ]; then printf '%s|' "$(stat -c %a <path>)"; sha256sum <path> | cut -d' ' -f1; else echo MISSING; fi. Returns Absent for missing files, Present { host, path, mode, sha256 } otherwise.
Apply behavior
Trace lines, one per resource:
[ssh] EXEC `uptime` on root@192.0.2.10
[ssh] FILE `motd` -> root@192.0.2.10:/etc/motd
[ssh] FILE `motd` on root@192.0.2.10 unchanged (sha256 match)