Home / OCI

OCIHow-toBeginner

Running scripts on OCI instances without SSH using Run Command

Use the Oracle Cloud Agent Run Command plugin to run a shell or PowerShell script on an instance with no SSH key, no bastion and no open port, and collect the output.

Deepak Bhardwaj6 min read

Applies toOCI Compute (Oracle Linux, Oracle Autonomous Linux, CentOS, Windows Server and custom images based on them), Oracle Cloud Agent Run Command plugin, OCI CLI 3.x

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:ALL means anyone who can create commands on this instance has root. Keep manage instance-agent-command-family limited to a small admin group, or list only the commands ocarun may run with sudo instead of ALL.

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 ocarun sudoers 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.