Home / OCI

OCITroubleshootingIntermediate

OCI instance cloud-init user data didn't run - debugging launch scripts and instance metadata

When a launch script on an OCI instance does nothing, check whether cloud-init ran, whether the user data reached the instance, and whether the script failed. Use the cloud-init logs and the instance metadata service.

Deepak Bhardwaj4 min read

Applies toOCI Compute Linux instances (Oracle Linux, Ubuntu and other platform images) with cloud-init; Instance Metadata Service v2

Symptoms

  • Packages, users or config from the launch script are missing.
  • An instance pool or Terraform deployment creates instances that never register with the app.
  • The script works when run by hand but not at launch.

Work through three layers: did the instance receive the user data, did cloud-init run, and did the script itself fail.

1. Did the instance receive the user data?

On the instance, query the metadata service (IMDSv2 needs the Authorization: Bearer Oracle header):

curl -s -H "Authorization: Bearer Oracle" http://169.254.169.254/opc/v2/instance/metadata/ | jq 'keys'
curl -s -H "Authorization: Bearer Oracle" http://169.254.169.254/opc/v2/instance/metadata/user_data | base64 -d | head -30

From outside the instance:

oci compute instance get --instance-id $ID --query 'data.metadata."user_data"' --raw-output | base64 -d | head
Finding Cause
No user_data key Script not passed at launch (wrong Terraform field or console box left empty)
Content is garbled after decoding Encoded twice, or not base64-encoded when calling the API directly
Content is correct Move on to cloud-init

In Terraform, user data goes in metadata = { user_data = base64encode(file("init.sh")) }. The console and oci compute instance launch --user-data-file do the encoding for you.

Warning

User data is readable by anyone on the instance through the metadata service, and by anyone with read access to the instance in OCI. Don't put passwords or keys in it. Fetch secrets from OCI Vault at runtime using instance principals.

2. Did cloud-init run?

cloud-init status --long
sudo cat /var/log/cloud-init-output.log | tail -50
sudo grep -i -E "error|warn|fail" /var/log/cloud-init.log | tail -30
cloud-init status Meaning
done Ran; check the output log for script errors
running Still running; long package installs can take minutes
error / degraded A module failed; the logs show which one
Command not found Image without cloud-init (custom or imported image)

3. Did the script fail?

The script output is in /var/log/cloud-init-output.log. The script itself is stored here:

sudo ls -l /var/lib/cloud/instance/scripts/
sudo cat /var/lib/cloud/instance/user-data.txt | head

Common failures:

Problem Fix
Missing shebang (#!/bin/bash) or #cloud-config first line cloud-init doesn't know how to run it
Windows line endings (CRLF) /bin/bash^M: bad interpreter; convert with dos2unix before uploading
dnf/apt fails No route to repositories; needs a NAT or service gateway, or OS Management Hub sources
sudo used and fails Scripts already run as root; remove sudo
Script expects a block volume that isn't attached yet Wait for the device or use systemd units after attachment
OCI CLI calls fail Use --auth instance_principal and check the dynamic group and policy

Run the script again by hand, exactly as cloud-init would:

sudo bash -x /var/lib/cloud/instance/scripts/part-001

4. cloud-config example

A #cloud-config file is easier to debug than a long shell script:

#cloud-config
package_update: true
packages:
  - nginx
  - jq
write_files:
  - path: /etc/myapp/env
    permissions: "0640"
    content: |
      APP_ENV=prod
runcmd:
  - systemctl enable --now nginx
  - firewall-cmd --permanent --add-service=http
  - firewall-cmd --reload

On Oracle Linux, remember the host firewall (firewalld). Opening a port in the security list isn't enough.

5. Re-running cloud-init

User data runs once per instance by default. To test changes on the same instance:

sudo cloud-init clean --logs
sudo cloud-init init && sudo cloud-init modules --mode=config && sudo cloud-init modules --mode=final
# or simply: sudo cloud-init clean --logs --reboot

Test on a throwaway instance. cloud-init clean also regenerates per-instance items such as SSH host keys on the next boot.

Instance pools and Terraform

  • For instance pools, the user data lives in the instance configuration. Updating the Terraform script doesn't change existing pool members; create a new instance configuration and update the pool.
  • Add a final line to the script that writes a marker, for example echo ok > /var/run/bootstrap.done, and have health checks or automation look for it.

Checklist

  • User data present in metadata and decodes correctly
  • cloud-init status checked; logs read
  • Shebang/cloud-config header and Unix line endings
  • Network path for package installs
  • No secrets in user data
  • Bootstrap marker for automation