AWS for DevOps — User Data Scripts

3 minute read

3.4 — User Data Scripts

User data is a script that runs automatically when an EC2 instance first boots. It is the simplest way to automate instance setup — install packages, configure services, and deploy applications without manual SSH.

How User Data Works

  Instance Launch
       │
       ▼
  OS boots
       │
       ▼
  cloud-init runs user data script (as root)
       │
       ▼
  Instance ready with software installed
  • Runs once at first boot (by default)
  • Runs as root — no sudo needed
  • Output is logged to /var/log/cloud-init-output.log
  • Maximum size: 16 KB (use S3 for larger scripts)

Basic User Data Example

Install Nginx and start it:

#!/bin/bash
yum update -y
yum install -y nginx
systemctl enable nginx
systemctl start nginx
echo "<h1>Hello from $(hostname)</h1>" > /usr/share/nginx/html/index.html

Launching with User Data (CLI)

# Method 1 — From a file
$ aws ec2 run-instances \
    --image-id ami-0abcdef1234567890 \
    --instance-type t3.micro \
    --key-name my-key-pair \
    --security-group-ids sg-0abc123def456 \
    --user-data file://setup.sh

# Method 2 — Inline (base64 encoded automatically by CLI)
$ aws ec2 run-instances \
    --image-id ami-0abcdef1234567890 \
    --instance-type t3.micro \
    --user-data '#!/bin/bash
yum update -y
yum install -y nginx
systemctl start nginx'

Real-World DevOps User Data Script

A production-ready script that installs Docker, pulls an app image, and starts it:

#!/bin/bash
set -euxo pipefail

# Variables
REGION="ap-south-1"
ECR_REPO="123456789012.dkr.ecr.${REGION}.amazonaws.com/my-app"
IMAGE_TAG="latest"

# Install Docker
yum update -y
yum install -y docker
systemctl enable docker
systemctl start docker

# Install CloudWatch Agent
yum install -y amazon-cloudwatch-agent
/opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl \
    -a fetch-config -m ec2 \
    -s -c ssm:AmazonCloudWatch-linux

# Login to ECR and pull the image
aws ecr get-login-password --region ${REGION} | \
    docker login --username AWS --password-stdin ${ECR_REPO}

docker pull ${ECR_REPO}:${IMAGE_TAG}

# Run the container
docker run -d \
    --name my-app \
    --restart unless-stopped \
    -p 80:8080 \
    ${ECR_REPO}:${IMAGE_TAG}

Note: The instance needs an IAM role with ECR pull permissions for this to work.

Checking User Data Execution

# SSH into the instance
$ ssh -i key.pem ec2-user@<ip>

# Check the user data log
$ sudo cat /var/log/cloud-init-output.log

# Check cloud-init status
$ cloud-init status
status: done

# If it failed:
$ cloud-init status --long
status: error
detail: ...

Retrieving User Data from an Instance

# From outside (via CLI)
$ aws ec2 describe-instance-attribute \
    --instance-id i-0abc123def456 \
    --attribute userData \
    --query 'UserData.Value' \
    --output text | base64 --decode

# From inside the instance (metadata)
$ TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" \
    -H "X-aws-ec2-metadata-token-ttl-seconds: 300")
$ curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
    http://169.254.169.254/latest/user-data

User Data vs AMI vs Configuration Management

Approach When to Use Pros Cons
User Data Simple setup, environment-specific config No custom AMI needed Slow boot (installs at launch)
Custom AMI Software is pre-installed Fast boot Must rebuild AMI for updates
AMI + User Data AMI has base software, user data does config Best of both worlds Two things to maintain
Config Management (Ansible, Chef) Complex multi-step setup Idempotent, testable Extra tooling to manage

Best Practice: AMI + User Data

  AMI (golden image):              User Data (per-instance):
  ┌───────────────────┐            ┌──────────────────────┐
  │  OS + patches     │            │  Pull latest app     │
  │  Docker engine    │            │  Set environment vars │
  │  CloudWatch agent │     +      │  Register with ALB   │
  │  Monitoring tools │            │  Start the app       │
  └───────────────────┘            └──────────────────────┘

The AMI handles things that rarely change (base OS, Docker, agents). User data handles things that change per deployment (app version, environment configuration).

Common Mistakes

Mistake Why It Hurts Fix
Not using set -e in the script Script continues after errors Always start with set -euxo pipefail
Installing heavy packages in user data Slow instance boot (5-10 minutes) Bake them into the AMI
Hard-coding secrets in user data User data is visible via metadata Use SSM Parameter Store or Secrets Manager
Forgetting to check logs You don’t know why it failed Always check /var/log/cloud-init-output.log
Exceeding 16 KB limit Script silently truncated Download large scripts from S3

Quick Reference

Task Command
Launch with user data aws ec2 run-instances ... --user-data file://setup.sh
View user data of instance aws ec2 describe-instance-attribute --instance-id ID --attribute userData
Check cloud-init logs (on instance) sudo cat /var/log/cloud-init-output.log
Check cloud-init status (on instance) cloud-init status --long

Next → Auto Scaling

Updated: