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