Free — no signup required

User Data vs. Cloud Config

3 min read

Injecting Personality

Beyond basic network setup, you want the server to actually do something: install Nginx, create a deploy user, write a config file, start a service. You provide these instructions via User Data — a blob of text you attach to the instance at launch time. Cloud-init reads this blob and acts on it.

You have two primary formats:

1. Shell Script (#!/bin/bash)

The simplest option. Cloud-init detects the shebang line and executes the file as a script under root — a handful of package-manager and systemctl calls to install and start a service, for instance.

  • Pros: Familiar to anyone who knows Bash. Easy to test locally. Full control over error handling with set -e, set -o pipefail, etc.
  • Cons: Imperative — you describe how to do things, not what the end state should be. Complex scripts become hard to maintain. No built-in idempotency.

2. Cloud-Config (#cloud-config)

A YAML document that describes the desired state of the system rather than the steps to reach it — a packages key listing nginx, say, instead of a package-manager install command. Cloud-init has built-in modules that interpret each key and handle the implementation details.

  • Pros: Declarative and readable. Built-in modules handle users, files, packages, and more. Easier to review in pull requests.
  • Cons: Strict YAML syntax — one indentation error silently breaks the entire boot. Less flexible than a full scripting language for complex logic.

The Header Is Not Optional:

Cloud-init determines the format entirely from the first line of the User Data blob:

First Line Interpretation
#!/bin/bash (or any shebang) Execute as a script
#cloud-config Parse as YAML cloud-config
#include Treat as a list of URLs to fetch and process
#cloud-boothook Run very early, before cloud-init's own stages
Anything else Usually ignored or treated as opaque text

If you omit the header or misspell #cloud-config (e.g., # cloud-config with a space), cloud-init will silently do nothing with your User Data. This is one of the most common causes of "my script didn't run" confusion.

Combining Both — Multi-Part MIME:

You can actually send both a shell script and a cloud-config in the same User Data blob using MIME multipart encoding. This is useful when you want cloud-config to handle users and packages, and a shell script to handle application-specific logic. Tools like write-mime-multipart (from the cloud-utils package) can assemble these for you.

Interview Tip

Interviewers sometimes ask: "What's the difference between runcmd in cloud-config and just using a shell script?" The key distinction is when they run and how errors are handled. Both execute in the Final stage, but runcmd commands run as a list within cloud-init's managed environment — cloud-init logs each command's output and exit code. A shell script runs as a child process; if it exits non-zero, cloud-init logs the failure but the instance still boots. Neither approach retries on failure by default. For production, prefer a configuration management tool (Ansible, Chef) invoked from User Data rather than complex inline scripts.

This is one of 18 chapters

Every chapter is free — Kubernetes, Terraform, SRE, distributed systems, and more — with fast daily review built in.

Sign up free