or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

cloudformation-init.mdec2-instances.mdflow-logs.mdindex.mdlaunch-templates.mdmachine-images.mdnetwork-acl.mdsecurity-groups.mdstorage-volumes.mdsubnets-networking.mduser-data.mdvpc-endpoints.mdvpc-management.mdvpn-connectivity.md
tile.json

user-data.mddocs/

User Data & Initialization

User data in AWS CDK EC2 provides a mechanism for passing startup scripts and configuration to EC2 instances during launch, enabling automated instance initialization and configuration.

UserData Abstract Class

The base class for user data implementations:

abstract class UserData {
  static forLinux(options?: LinuxUserDataOptions): UserData;
  static forWindows(): UserData;
  static custom(content: string): UserData;
  
  abstract addCommands(...commands: string[]): void;
  abstract render(): string;
  
  // Additional utility methods
  addExecuteFileCommand(params: ExecuteFileOptions): void;
  addS3DownloadCommand(params: S3DownloadOptions): void;
  addSignalOnExitCommand(resource: Resource): void;
  addOnExitCommands(...commands: string[]): void;
}

Linux User Data

Specialized class for Linux instances:

class LinuxUserData extends UserData {
  constructor(options?: LinuxUserDataOptions);
  
  addCommands(...commands: string[]): void;
  render(): string;
  
  // Linux-specific methods
  addSignalOnExitCommand(resource: Resource): void;
  addOnExitCommands(...commands: string[]): void;
}

interface LinuxUserDataOptions {
  readonly shebang?: string;
}

Windows User Data

Specialized class for Windows instances:

class WindowsUserData extends UserData {
  constructor();
  
  addCommands(...commands: string[]): void;
  render(): string;
  
  // Windows-specific methods
  addSignalOnExitCommand(resource: Resource): void;
  addOnExitCommands(...commands: string[]): void;
}

Utility Interfaces

Supporting interfaces for advanced user data operations:

interface ExecuteFileOptions {
  readonly filePath: string;
  readonly arguments?: string;
}

interface S3DownloadOptions {
  readonly bucket: s3.IBucket;
  readonly bucketKey: string;
  readonly localFile?: string;
  readonly region?: string;
}

CloudFormation Init Integration

CloudFormation Init provides more advanced instance initialization:

class CloudFormationInit {
  static fromElements(...elements: InitElement[]): CloudFormationInit;
  static fromConfig(config: InitConfig): CloudFormationInit;
  static fromConfigSets(props: ConfigSetProps): CloudFormationInit;
  
  addConfig(configName: string, config: InitConfig): void;
  addConfigSet(configSetName: string, configNames: string[]): void;
  attach(attachedResource: CfnResource, options: AttachInitOptions): void;
}

class InitConfig {
  constructor(elements: InitElement[]);
  add(...elements: InitElement[]): void;
}

interface ConfigSetProps {
  readonly configSets: Record<string, string[]>;
  readonly configs: Record<string, InitConfig>;
}

interface AttachInitOptions {
  readonly platform?: InitPlatform;
  readonly userData?: UserData;
  readonly ignoreFailures?: boolean;
  readonly includeUrl?: boolean;
  readonly includeRole?: boolean;
  readonly printLog?: boolean;
  readonly configSets?: string[];
}

interface ApplyCloudFormationInitOptions {
  readonly timeout?: Duration;
  readonly includeUrl?: boolean;
  readonly includeRole?: boolean;
  readonly printLog?: boolean;
  readonly ignoreFailures?: boolean;
  readonly configSets?: string[];
}

enum InitPlatform {
  LINUX = 'linux',
  WINDOWS = 'windows'
}

Usage Examples

Basic Linux User Data

import * as ec2 from "@aws-cdk/aws-ec2";

const vpc = new ec2.Vpc(this, "MyVpc");

// Create basic Linux user data
const userData = ec2.UserData.forLinux();
userData.addCommands(
  "yum update -y",
  "yum install -y httpd",
  "systemctl start httpd",
  "systemctl enable httpd",
  "echo '<h1>Hello from EC2!</h1>' > /var/www/html/index.html"
);

const instance = new ec2.Instance(this, "WebServer", {
  vpc,
  instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MICRO),
  machineImage: ec2.MachineImage.latestAmazonLinux(),
  userData
});

Windows User Data

const windowsUserData = ec2.UserData.forWindows();
windowsUserData.addCommands(
  "powershell -Command Install-WindowsFeature -name Web-Server -IncludeManagementTools",
  "powershell -Command New-Item -Path C:\\inetpub\\wwwroot\\index.html -ItemType File -Force",
  "powershell -Command Set-Content -Path C:\\inetpub\\wwwroot\\index.html -Value '<h1>Hello from Windows Server!</h1>'"
);

const windowsInstance = new ec2.Instance(this, "WindowsWebServer", {
  vpc,
  instanceType: ec2.InstanceType.of(ec2.InstanceClass.M5, ec2.InstanceSize.LARGE),
  machineImage: ec2.MachineImage.latestWindows(
    ec2.WindowsVersion.WINDOWS_SERVER_2022_ENGLISH_FULL_BASE
  ),
  userData: windowsUserData,
  keyName: "windows-keypair"
});

User Data with Custom Shebang

const customUserData = ec2.UserData.forLinux({
  shebang: "#!/bin/bash -xe"
});

customUserData.addCommands(
  "exec > >(tee /var/log/user-data.log|logger -t user-data -s 2>/dev/console) 2>&1",
  "yum update -y",
  "yum install -y docker",
  "systemctl start docker",
  "systemctl enable docker",
  "usermod -a -G docker ec2-user"
);

User Data with File Download from S3

import * as s3 from "@aws-cdk/aws-s3";

const configBucket = s3.Bucket.fromBucketName(this, "ConfigBucket", "my-config-bucket");

const userData = ec2.UserData.forLinux();

// Download configuration file from S3
userData.addS3DownloadCommand({
  bucket: configBucket,
  bucketKey: "configs/app-config.json",
  localFile: "/opt/app/config.json"
});

// Download and execute script from S3
userData.addS3DownloadCommand({
  bucket: configBucket,
  bucketKey: "scripts/setup.sh",
  localFile: "/tmp/setup.sh"
});

userData.addExecuteFileCommand({
  filePath: "/tmp/setup.sh",
  arguments: "--environment production"
});

userData.addCommands(
  "chmod +x /tmp/setup.sh",
  "/tmp/setup.sh --environment production"
);

Docker Installation and Container Setup

const dockerUserData = ec2.UserData.forLinux();
dockerUserData.addCommands(
  // Update system
  "yum update -y",
  
  // Install Docker
  "amazon-linux-extras install docker -y",
  "systemctl start docker",
  "systemctl enable docker",
  "usermod -a -G docker ec2-user",
  
  // Install Docker Compose
  "curl -L \"https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)\" -o /usr/local/bin/docker-compose",
  "chmod +x /usr/local/bin/docker-compose",
  
  // Pull and run container
  "docker pull nginx:latest",
  "docker run -d -p 80:80 --name webserver nginx:latest",
  
  // Create custom index page
  "docker exec webserver sh -c 'echo \"<h1>Hello from Docker on EC2!</h1>\" > /usr/share/nginx/html/index.html'"
);

const dockerInstance = new ec2.Instance(this, "DockerInstance", {
  vpc,
  instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.SMALL),
  machineImage: ec2.MachineImage.latestAmazonLinux(),
  userData: dockerUserData
});

Database Server Setup

const dbUserData = ec2.UserData.forLinux();
dbUserData.addCommands(
  "yum update -y",
  
  // Install MySQL
  "yum install -y mysql-server",
  "systemctl start mysqld",
  "systemctl enable mysqld",
  
  // Secure installation (basic setup)
  "mysql -e \"ALTER USER 'root'@'localhost' IDENTIFIED BY 'MySecurePassword123!';\"",
  "mysql -e \"DELETE FROM mysql.user WHERE User='';\"",
  "mysql -e \"DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1');\"",
  "mysql -e \"DROP DATABASE IF EXISTS test;\"",
  "mysql -e \"DELETE FROM mysql.db WHERE Db='test' OR Db='test\\_%';\"",
  "mysql -e \"FLUSH PRIVILEGES;\"",
  
  // Create application database
  "mysql -u root -pMySecurePassword123! -e \"CREATE DATABASE app_db;\"",
  "mysql -u root -pMySecurePassword123! -e \"CREATE USER 'app_user'@'%' IDENTIFIED BY 'AppPassword123!';\"",
  "mysql -u root -pMySecurePassword123! -e \"GRANT ALL PRIVILEGES ON app_db.* TO 'app_user'@'%';\"",
  "mysql -u root -pMySecurePassword123! -e \"FLUSH PRIVILEGES;\""
);

const dbInstance = new ec2.Instance(this, "DatabaseInstance", {
  vpc,
  instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.SMALL),
  machineImage: ec2.MachineImage.latestAmazonLinux(),
  userData: dbUserData,
  vpcSubnets: {
    subnetType: ec2.SubnetType.PRIVATE_WITH_NAT
  }
});

User Data with CloudWatch Agent

const monitoringUserData = ec2.UserData.forLinux();
monitoringUserData.addCommands(
  "yum update -y",
  
  // Install CloudWatch agent
  "wget https://s3.amazonaws.com/amazoncloudwatch-agent/amazon_linux/amd64/latest/amazon-cloudwatch-agent.rpm",
  "rpm -U ./amazon-cloudwatch-agent.rpm",
  
  // Create CloudWatch agent configuration
  "cat <<EOF > /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json",
  "{",
  "  \"agent\": {",
  "    \"metrics_collection_interval\": 60,",
  "    \"run_as_user\": \"cwagent\"",
  "  },",
  "  \"metrics\": {",
  "    \"namespace\": \"MyApp/EC2\",",
  "    \"metrics_collected\": {",
  "      \"cpu\": {",
  "        \"measurement\": [",
  "          \"cpu_usage_idle\",",
  "          \"cpu_usage_iowait\",",
  "          \"cpu_usage_user\",",
  "          \"cpu_usage_system\"",
  "        ],",
  "        \"metrics_collection_interval\": 60",
  "      },",
  "      \"disk\": {",
  "        \"measurement\": [",
  "          \"used_percent\"",
  "        ],",
  "        \"metrics_collection_interval\": 60,",
  "        \"resources\": [",
  "          \"*\"",
  "        ]",
  "      },",
  "      \"mem\": {",
  "        \"measurement\": [",
  "          \"mem_used_percent\"",
  "        ],",
  "        \"metrics_collection_interval\": 60",
  "      }",
  "    }",
  "  },",
  "  \"logs\": {",
  "    \"logs_collected\": {",
  "      \"files\": {",
  "        \"collect_list\": [",
  "          {",
  "            \"file_path\": \"/var/log/messages\",",
  "            \"log_group_name\": \"/aws/ec2/var/log/messages\",",
  "            \"log_stream_name\": \"{instance_id}\"",
  "          }",
  "        ]",
  "      }",
  "    }",
  "  }",
  "}",
  "EOF",
  
  // Start CloudWatch agent
  "/opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -c file:/opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json -s"
);

CloudFormation Init Configuration

import * as cdk from "@aws-cdk/core";

const init = ec2.CloudFormationInit.fromElements(
  // Install packages
  ec2.InitPackage.yum("httpd"),
  ec2.InitPackage.yum("mysql"),
  
  // Create files
  ec2.InitFile.fromString(
    "/var/www/html/index.html",
    "<html><body><h1>Hello from CloudFormation Init!</h1></body></html>"
  ),
  
  ec2.InitFile.fromAsset(
    "/etc/httpd/conf.d/custom.conf",
    "assets/httpd-custom.conf"
  ),
  
  // Configure services
  ec2.InitService.enable("httpd", {
    enabled: true,
    ensureRunning: true,
    serviceRestartHandle: new ec2.InitServiceRestartHandle()
  }),
  
  // Run commands
  ec2.InitCommand.shellCommand("echo 'Setup complete' > /tmp/setup.log")
);

const initInstance = new ec2.Instance(this, "InitInstance", {
  vpc,
  instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MICRO),
  machineImage: ec2.MachineImage.latestAmazonLinux(),
  init,
  initOptions: {
    timeout: cdk.Duration.minutes(10),
    includeUrl: true,
    includeRole: true,
    printLog: true
  }
});

Multi-Stage Init with Config Sets

const webConfig = new ec2.InitConfig([
  ec2.InitPackage.yum("httpd"),
  ec2.InitService.enable("httpd")
]);

const appConfig = new ec2.InitConfig([
  ec2.InitPackage.yum("python3"),
  ec2.InitFile.fromString("/opt/app/app.py", "print('Hello World')"),
  ec2.InitCommand.shellCommand("python3 /opt/app/app.py")
]);

const dbConfig = new ec2.InitConfig([
  ec2.InitPackage.yum("mysql-server"),
  ec2.InitService.enable("mysqld")
]);

const multiStageInit = ec2.CloudFormationInit.fromConfigSets({
  configSets: {
    "setup": ["web", "app"],
    "full": ["web", "app", "database"]
  },
  configs: {
    "web": webConfig,
    "app": appConfig,
    "database": dbConfig
  }
});

const multiStageInstance = new ec2.Instance(this, "MultiStageInstance", {
  vpc,
  instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.SMALL),
  machineImage: ec2.MachineImage.latestAmazonLinux(),
  init: multiStageInit,
  initOptions: {
    configSets: ["full"],
    timeout: cdk.Duration.minutes(15)
  }
});

User Data with Resource Signaling

const signalUserData = ec2.UserData.forLinux();
signalUserData.addCommands(
  "yum update -y",
  "yum install -y httpd",
  "systemctl start httpd",
  "systemctl enable httpd"
);

// Signal successful completion
const waitConditionHandle = new cdk.CfnWaitConditionHandle(this, "WaitHandle");
const waitCondition = new cdk.CfnWaitCondition(this, "WaitCondition", {
  handle: waitConditionHandle.ref,
  timeout: "300",
  count: 1
});

signalUserData.addSignalOnExitCommand(waitCondition);

const signalInstance = new ec2.Instance(this, "SignalInstance", {
  vpc,
  instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MICRO),
  machineImage: ec2.MachineImage.latestAmazonLinux(),
  userData: signalUserData
});

Custom User Data Content

// Raw user data content
const customContent = `#!/bin/bash
yum update -y
yum install -y git

# Clone repository
cd /opt
git clone https://github.com/myorg/myapp.git

# Setup application
cd myapp
./setup.sh

# Start services
systemctl start myapp
systemctl enable myapp
`;

const customUserData = ec2.UserData.custom(customContent);

const customInstance = new ec2.Instance(this, "CustomInstance", {
  vpc,
  instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MICRO),
  machineImage: ec2.MachineImage.latestAmazonLinux(),
  userData: customUserData
});

Environment-Specific Configuration

const environment = "production"; // Could be parameter or context value

const envUserData = ec2.UserData.forLinux();

// Base setup
envUserData.addCommands(
  "yum update -y",
  "yum install -y httpd"
);

// Environment-specific configuration
if (environment === "production") {
  envUserData.addCommands(
    "yum install -y aws-logs-agent",
    "systemctl start awslogs",
    "systemctl enable awslogs"
  );
} else {
  envUserData.addCommands(
    "yum install -y htop",
    "echo 'Development environment' > /var/www/html/env.html"
  );
}

// Common final steps
envUserData.addCommands(
  "systemctl start httpd",
  "systemctl enable httpd"
);

User Data with Error Handling

const robustUserData = ec2.UserData.forLinux({
  shebang: "#!/bin/bash -xe"
});

robustUserData.addCommands(
  // Enable error logging
  "exec > >(tee /var/log/user-data.log|logger -t user-data -s 2>/dev/console) 2>&1",
  
  // Function for error handling
  "function error_exit {",
  "  echo \"$1\" 1>&2",
  "  exit 1",
  "}",
  
  // Safe package installation
  "yum update -y || error_exit 'Failed to update packages'",
  "yum install -y httpd || error_exit 'Failed to install httpd'",
  
  // Service management with error checking
  "systemctl start httpd || error_exit 'Failed to start httpd'",
  "systemctl enable httpd || error_exit 'Failed to enable httpd'",
  
  // Verify service is running
  "systemctl is-active --quiet httpd || error_exit 'httpd is not running'",
  
  "echo 'User data completed successfully' > /tmp/user-data-success"
);

Best Practices

  1. Error Handling: Use proper error handling and logging in user data scripts
  2. Idempotency: Write scripts that can be run multiple times safely
  3. Logging: Log user data execution for troubleshooting
  4. Size Limits: Keep user data under 16KB (base64 encoded)
  5. Security: Avoid hardcoding secrets in user data - use Parameter Store or Secrets Manager
  6. Testing: Test user data scripts thoroughly before deployment
  7. CloudFormation Init: Use CloudFormation Init for complex configurations
  8. Resource Signaling: Use resource signaling for deployment coordination
  9. Platform-Specific: Use appropriate user data classes for Linux vs Windows
  10. Modularity: Break complex setups into smaller, reusable components