Goal
You need to run a command on an instance (check disk space, restart a service, collect a log) and you can't, or don't want to, SSH in. Maybe the key is lost, the bastion is down, or policy says no interactive access to production.
Run Command lets you send a script from the console or CLI. The Oracle Cloud Agent on the instance polls OCI for commands, runs them, and reports the exit code and output. No inbound port is needed.
How it works
| Piece | What it does |
|---|---|
| Run Command plugin | Part of Oracle Cloud Agent. Polls for commands and runs them |
ocarun user (Linux) / NT SERVICE\OCARUN (Windows) | The account the script runs as |
| Dynamic group + policy | Lets the instance fetch and run commands |
| Group policy | Lets you create, cancel and view commands |
| Object Storage (optional) | Holds scripts over 4 KB and full output |
Limits to know before you start:
- Inline script text: up to 4 KB. Larger scripts must come from Object Storage.
- Text output: only the last 1 KB is kept, for 7 days. Send output to a bucket if you need all of it.
- Timeout: default 1 hour, maximum 24 hours.
- Each command runs once. It's not a scheduler.
1. Check the instance is ready
The plugin must be enabled and running:
oci instance-agent plugin get \
--instanceagent-id ocid1.instance.oc1..xxxx \
--compartment-id ocid1.compartment.oc1..xxxx \
--plugin-name "Compute Instance Run Command"
Look for "status": "RUNNING". If it's STOPPED, enable it on the instance's Oracle Cloud Agent tab. If it's INVALID or missing, the agent itself has a problem, such as no route to OCI. Fix the agent first.
The instance needs outbound HTTPS to OCI, through a service gateway, NAT gateway or internet gateway.
2. Set up IAM
Create a dynamic group for the instances that can receive commands. Matching by compartment is simplest:
ALL {instance.compartment.id = 'ocid1.compartment.oc1..xxxx'}
Policies (replace names and compartment):
Allow group OpsAdmins to manage instance-agent-command-family in compartment app-prod
Allow dynamic-group app-prod-instances to use instance-agent-command-execution-family in compartment app-prod where request.instance.id=target.instance.id
If scripts or output use Object Storage, add:
Allow dynamic-group app-prod-instances to read objects in compartment app-prod where all {target.bucket.name = 'runcmd-scripts'}
Allow dynamic-group app-prod-instances to manage objects in compartment app-prod where all {target.bucket.name = 'runcmd-output'}
Tip
If you add an existing instance to a dynamic group, it can take up to 30 minutes before it starts polling. An instance created after the dynamic group exists starts polling straight away. Restarting the agent can speed it up.
3. Run a simple command
Write the target and content as JSON files. Inline text, output as text:
cat > target.json <<'EOF'
{ "instanceId": "ocid1.instance.oc1..xxxx" }
EOF
cat > content.json <<'EOF'
{
"source": { "sourceType": "TEXT", "text": "df -h /; uptime; systemctl is-active nginx" },
"output": { "outputType": "TEXT" }
}
EOF
CMD=$(oci instance-agent command create \
--compartment-id ocid1.compartment.oc1..xxxx \
--display-name "disk-and-nginx-check" \
--timeout-in-seconds 600 \
--target file://target.json \
--content file://content.json \
--query 'data.id' --raw-output)
echo "$CMD"
If your CLI version complains about the JSON, generate the expected shape with oci instance-agent command create --generate-param-json-input content.
Get the result:
oci instance-agent command-execution get \
--command-id "$CMD" \
--instance-id ocid1.instance.oc1..xxxx \
--query 'data.{state:"lifecycle-state",exit:content."exit-code",output:content.text}'
The state moves from ACCEPTED to IN_PROGRESS to SUCCEEDED, FAILED or TIMED_OUT. Exit code 0 means success. Commands stay ACCEPTED if the instance never picks them up, which usually means IAM (step 2) or the agent (step 1).
4. Run as root
Scripts run as ocarun, which has no admin rights by default. sudo fails until you grant it. On Linux, with your normal admin access once:
echo 'ocarun ALL=(ALL) NOPASSWD:ALL' > ./101-oracle-cloud-agent-run-command
visudo -cf ./101-oracle-cloud-agent-run-command # expect: parsed OK
sudo cp ./101-oracle-cloud-agent-run-command /etc/sudoers.d/
Warning
NOPASSWD:ALLmeans anyone who can create commands on this instance has root. Keepmanage instance-agent-command-familylimited to a small admin group, or list only the commandsocarunmay run with sudo instead ofALL.
Bake this file into your custom image so new instances are ready.
On Windows, add the service account to Administrators and restart the service:
Add-LocalGroupMember -Group "Administrators" -Member "NT SERVICE\OCARUN"
Restart-Service -Name OCARUN -Force
5. PowerShell on Windows
The default shell on Windows is batch. Start the script with #ps1 to run PowerShell:
#ps1
Get-Service W3SVC | Select-Object Status, Name
Get-PSDrive C | Select-Object Used, Free
Pass it as the text field the same way as step 3.
6. Bigger scripts and full output
Upload the script, then point the command at it and send output to a bucket:
oci os object put --bucket-name runcmd-scripts --file collect-logs.sh --name collect-logs.sh
NS=$(oci os ns get --query data --raw-output)
cat > content.json <<EOF
{
"source": {
"sourceType": "OBJECT_STORAGE_TUPLE",
"bucketName": "runcmd-scripts",
"namespaceName": "$NS",
"objectName": "collect-logs.sh"
},
"output": {
"outputType": "OBJECT_STORAGE_TUPLE",
"bucketName": "runcmd-output",
"namespaceName": "$NS",
"objectName": "web01/collect-logs-$(date +%Y%m%d%H%M).txt"
}
}
EOF
Create the command as in step 3, then download the output with oci os object get. The bucket must be in the same region as the instance.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
Command stays ACCEPTED | Instance not polling | Check the plugin is RUNNING, the dynamic group rule matches, and wait up to 30 minutes |
NotAuthorizedOrNotFound ... 404 in the plugin log | Dynamic group policy missing or not yet active | Fix step 2, then restart the agent |
sudo: a password is required | No sudoers entry for ocarun | Step 4 |
| Output cut off | Text output keeps only the last 1 KB | Send output to Object Storage |
TIMED_OUT | Script ran longer than the timeout | Raise --timeout-in-seconds or split the work |
On Linux, the plugin log is the first place to look:
sudo tail -50 /var/log/oracle-cloud-agent/plugins/runcommand/runcommand.log
sudo systemctl restart oracle-cloud-agent
Caution
Command text, names and text output are stored by OCI and visible to anyone who can read commands in the compartment. Don't put passwords or keys in scripts. Have the script read them from OCI Vault using the instance principal.
Prevent it next time
- Enable the Run Command plugin and the
ocarunsudoers file in your standard image, so you have a way in when SSH breaks. - Keep the dynamic group and policies in Terraform with the compartment, not as one-off console changes.
- Keep a small library of tested diagnostic scripts in a bucket (disk, memory, service status, log collection) so on-call can run them quickly.
- Review who has
manage instance-agent-command-family. In practice it's the same as root on every instance in the compartment.