AnsiblePilot — Master Ansible Automation

AnsiblePilot is the leading resource for learning Ansible automation, DevOps, and infrastructure as code. Browse over 1,400 tutorials covering Ansible modules, playbooks, roles, collections, and real-world examples. Whether you are a beginner or an experienced engineer, our step-by-step guides help you automate Linux, Windows, cloud, containers, and network infrastructure.

Popular Topics

About Luca Berton

Luca Berton is an Ansible automation expert, author of 8 Ansible books published by Apress and Leanpub including "Ansible for VMware by Examples" and "Ansible for Kubernetes by Example", and creator of the Ansible Pilot YouTube channel. He shares practical automation knowledge through tutorials, books, and video courses to help IT professionals and DevOps engineers master infrastructure automation.

Ansible for AWS: Complete Guide to Cloud Automation with EC2, S3, RDS, and More

By Luca Berton · Published 2024-01-01 · Category: installation

Automate AWS with Ansible. Provision EC2 instances, manage S3 buckets, configure RDS databases, deploy VPCs, set up ELBs, and orchestrate complete AWS.

Getting Started with Ansible + AWS

# Install AWS collection and dependencies
ansible-galaxy collection install amazon.aws
pip install boto3 botocore

# Configure credentials export AWS_ACCESS_KEY_ID="your-access-key" export AWS_SECRET_ACCESS_KEY="your-secret-key" export AWS_DEFAULT_REGION="us-east-1"

# Or use ~/.aws/credentials (preferred) aws configure

See also: Ansible AWS: Complete Guide to Cloud Automation (2026)

VPC and Networking

---
- name: Create AWS VPC infrastructure
  hosts: localhost
  connection: local

vars: project: myapp region: us-east-1 vpc_cidr: 10.0.0.0/16 public_subnets: - { cidr: "10.0.1.0/24", az: "us-east-1a" } - { cidr: "10.0.2.0/24", az: "us-east-1b" } private_subnets: - { cidr: "10.0.10.0/24", az: "us-east-1a" } - { cidr: "10.0.11.0/24", az: "us-east-1b" }

tasks: - name: Create VPC amazon.aws.ec2_vpc_net: name: "{{ project }}-vpc" cidr_block: "{{ vpc_cidr }}" region: "{{ region }}" tags: Project: "{{ project }}" Environment: production register: vpc

- name: Create internet gateway amazon.aws.ec2_vpc_igw: vpc_id: "{{ vpc.vpc.id }}" region: "{{ region }}" tags: Name: "{{ project }}-igw" register: igw

- name: Create public subnets amazon.aws.ec2_vpc_subnet: vpc_id: "{{ vpc.vpc.id }}" cidr: "{{ item.cidr }}" az: "{{ item.az }}" map_public: true region: "{{ region }}" tags: Name: "{{ project }}-public-{{ item.az }}" Type: public loop: "{{ public_subnets }}" register: public_subnet_results

- name: Create private subnets amazon.aws.ec2_vpc_subnet: vpc_id: "{{ vpc.vpc.id }}" cidr: "{{ item.cidr }}" az: "{{ item.az }}" region: "{{ region }}" tags: Name: "{{ project }}-private-{{ item.az }}" Type: private loop: "{{ private_subnets }}" register: private_subnet_results

- name: Create NAT gateway amazon.aws.ec2_vpc_nat_gateway: subnet_id: "{{ public_subnet_results.results[0].subnet.id }}" region: "{{ region }}" if_exist_do_not_create: true tags: Name: "{{ project }}-nat" register: nat_gw

- name: Create public route table amazon.aws.ec2_vpc_route_table: vpc_id: "{{ vpc.vpc.id }}" region: "{{ region }}" subnets: "{{ public_subnet_results.results | map(attribute='subnet.id') | list }}" routes: - dest: 0.0.0.0/0 gateway_id: "{{ igw.gateway_id }}" tags: Name: "{{ project }}-public-rt"

- name: Create private route table amazon.aws.ec2_vpc_route_table: vpc_id: "{{ vpc.vpc.id }}" region: "{{ region }}" subnets: "{{ private_subnet_results.results | map(attribute='subnet.id') | list }}" routes: - dest: 0.0.0.0/0 nat_gateway_id: "{{ nat_gw.nat_gateway_id }}" tags: Name: "{{ project }}-private-rt"

Security Groups

    - name: Create web security group
      amazon.aws.ec2_security_group:
        name: "{{ project }}-web-sg"
        description: "Web server security group"
        vpc_id: "{{ vpc.vpc.id }}"
        region: "{{ region }}"
        rules:
          - proto: tcp
            from_port: 80
            to_port: 80
            cidr_ip: 0.0.0.0/0
            rule_desc: "HTTP"
          - proto: tcp
            from_port: 443
            to_port: 443
            cidr_ip: 0.0.0.0/0
            rule_desc: "HTTPS"
          - proto: tcp
            from_port: 22
            to_port: 22
            cidr_ip: 10.0.0.0/16
            rule_desc: "SSH from VPC"
        tags:
          Name: "{{ project }}-web-sg"
      register: web_sg

- name: Create database security group amazon.aws.ec2_security_group: name: "{{ project }}-db-sg" description: "Database security group" vpc_id: "{{ vpc.vpc.id }}" region: "{{ region }}" rules: - proto: tcp from_port: 5432 to_port: 5432 group_id: "{{ web_sg.group_id }}" rule_desc: "PostgreSQL from web servers" tags: Name: "{{ project }}-db-sg" register: db_sg

See also: Ansible AWS EC2: Automate Ubuntu Instance Creation & Data Collection

EC2 Instances

    - name: Create EC2 key pair
      amazon.aws.ec2_key:
        name: "{{ project }}-key"
        region: "{{ region }}"
      register: ec2_key

- name: Save private key ansible.builtin.copy: content: "{{ ec2_key.key.private_key }}" dest: "~/.ssh/{{ project }}-key.pem" mode: '0600' when: ec2_key.changed

- name: Launch web server instances amazon.aws.ec2_instance: name: "{{ project }}-web-{{ item }}" instance_type: t3.medium image_id: ami-0c7217cdde317cfec # Ubuntu 22.04 key_name: "{{ project }}-key" vpc_subnet_id: "{{ public_subnet_results.results[item | int - 1].subnet.id }}" security_groups: - "{{ web_sg.group_id }}" network: assign_public_ip: true volumes: - device_name: /dev/sda1 ebs: volume_size: 50 volume_type: gp3 encrypted: true tags: Project: "{{ project }}" Role: web Environment: production user_data: | #!/bin/bash apt-get update && apt-get install -y python3 wait: true region: "{{ region }}" loop: ["1", "2"] register: ec2_instances

S3 Buckets

    - name: Create S3 bucket
      amazon.aws.s3_bucket:
        name: "{{ project }}-assets-{{ aws_account_id }}"
        region: "{{ region }}"
        versioning: true
        encryption: AES256
        public_access:
          block_public_acls: true
          block_public_policy: true
          ignore_public_acls: true
          restrict_public_buckets: true
        tags:
          Project: "{{ project }}"

- name: Configure bucket lifecycle amazon.aws.s3_lifecycle: name: "{{ project }}-assets-{{ aws_account_id }}" rule_id: "archive-old-versions" noncurrent_version_transition_days: 30 noncurrent_version_storage_class: GLACIER noncurrent_version_expiration_days: 365 status: enabled

- name: Upload files to S3 amazon.aws.s3_object: bucket: "{{ project }}-assets-{{ aws_account_id }}" object: "config/app.yml" src: files/app-config.yml mode: put encryption: AES256

- name: Sync directory to S3 ansible.builtin.command: cmd: > aws s3 sync ./static/ s3://{{ project }}-assets-{{ aws_account_id }}/static/ --delete changed_when: true

See also: Configuring Ansible for AWS: Setup Guide & Playbook

RDS Database

    - name: Create RDS subnet group
      amazon.aws.rds_subnet_group:
        name: "{{ project }}-db-subnet"
        description: "Database subnet group"
        subnets: "{{ private_subnet_results.results | map(attribute='subnet.id') | list }}"
        region: "{{ region }}"

- name: Create RDS PostgreSQL instance amazon.aws.rds_instance: id: "{{ project }}-db" instance_type: db.r6g.large engine: postgres engine_version: "16.4" master_username: dbadmin master_user_password: "{{ vault_rds_password }}" allocated_storage: 100 max_allocated_storage: 500 storage_type: gp3 storage_encrypted: true multi_az: true db_subnet_group_name: "{{ project }}-db-subnet" vpc_security_group_ids: - "{{ db_sg.group_id }}" backup_retention_period: 14 preferred_backup_window: "03:00-04:00" preferred_maintenance_window: "sun:04:00-sun:05:00" auto_minor_version_upgrade: true publicly_accessible: false tags: Project: "{{ project }}" Environment: production region: "{{ region }}" wait: true register: rds_instance

- name: Create read replica amazon.aws.rds_instance: id: "{{ project }}-db-replica" instance_type: db.r6g.large source_db_instance_identifier: "{{ project }}-db" region: "{{ region }}" tags: Project: "{{ project }}" Role: read-replica

Application Load Balancer

    - name: Create target group
      community.aws.elb_target_group:
        name: "{{ project }}-tg"
        protocol: http
        port: 80
        vpc_id: "{{ vpc.vpc.id }}"
        health_check_path: /health
        health_check_interval: 30
        healthy_threshold_count: 2
        unhealthy_threshold_count: 3
        targets: "{{ ec2_instances.results | map(attribute='instances') | flatten | map(attribute='instance_id') | map('community.general.dict_kv', 'Id') | list }}"
        region: "{{ region }}"
        state: present

- name: Create ALB amazon.aws.elb_application_lb: name: "{{ project }}-alb" subnets: "{{ public_subnet_results.results | map(attribute='subnet.id') | list }}" security_groups: - "{{ web_sg.group_id }}" listeners: - Protocol: HTTPS Port: 443 SslPolicy: ELBSecurityPolicy-TLS13-1-2-2021-06 Certificates: - CertificateArn: "{{ acm_cert_arn }}" DefaultActions: - Type: forward TargetGroupName: "{{ project }}-tg" - Protocol: HTTP Port: 80 DefaultActions: - Type: redirect RedirectConfig: Protocol: HTTPS Port: "443" StatusCode: HTTP_301 region: "{{ region }}" state: present

IAM Roles

    - name: Create EC2 instance role
      amazon.aws.iam_role:
        name: "{{ project }}-ec2-role"
        assume_role_policy_document: |
          {
            "Version": "2012-10-17",
            "Statement": [{
              "Effect": "Allow",
              "Principal": {"Service": "ec2.amazonaws.com"},
              "Action": "sts:AssumeRole"
            }]
          }
        managed_policies:
          - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
          - arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy
        tags:
          Project: "{{ project }}"

- name: Create S3 access policy amazon.aws.iam_policy: iam_type: role iam_name: "{{ project }}-ec2-role" policy_name: "{{ project }}-s3-access" policy_json: | { "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Action": ["s3:GetObject", "s3:ListBucket"], "Resource": [ "arn:aws:s3:::{{ project }}-assets-*", "arn:aws:s3:::{{ project }}-assets-*/*" ] }] } state: present

Complete Infrastructure Teardown

---
- name: Destroy all AWS resources
  hosts: localhost
  connection: local

tasks: - name: Terminate EC2 instances amazon.aws.ec2_instance: filters: "tag:Project": "{{ project }}" state: absent wait: true

- name: Delete RDS instances amazon.aws.rds_instance: id: "{{ item }}" skip_final_snapshot: true state: absent wait: true loop: - "{{ project }}-db-replica" - "{{ project }}-db"

- name: Delete ALB amazon.aws.elb_application_lb: name: "{{ project }}-alb" state: absent wait: true

# Continue with S3, VPC, etc.

FAQ

Should I use Ansible or Terraform for AWS?

Use both. Terraform excels at infrastructure provisioning (VPCs, EC2, RDS) — it tracks state and handles dependencies well. Ansible excels at configuration management (installing software, deploying apps, managing users). Terraform provisions the EC2 instance; Ansible configures what's on it.

How do I use IAM roles instead of access keys?

If running Ansible on an EC2 instance, attach an IAM role to the instance. The amazon.aws collection automatically uses instance metadata credentials. No access keys needed — more secure and no credential rotation.

How do I handle multiple AWS accounts?

Use profile parameter in modules or set AWS_PROFILE environment variable. For cross-account access, use sts_assume_role to assume roles in target accounts:

- amazon.aws.sts_assume_role:
    role_arn: "arn:aws:iam::123456789:role/CrossAccountRole"
    role_session_name: "ansible"
  register: assumed_role

- amazon.aws.ec2_instance: # ... aws_access_key: "{{ assumed_role.sts_creds.access_key }}" aws_secret_key: "{{ assumed_role.sts_creds.secret_key }}" security_token: "{{ assumed_role.sts_creds.session_token }}"

How do I make Ansible AWS playbooks idempotent?

Most amazon.aws modules are idempotent by default — they check current state before making changes. Use name or tags for identification, not instance IDs. Always use state: present (not create) to ensure idempotency.

Conclusion

Ansible automates the complete AWS stack — VPC networking, EC2 instances, S3 storage, RDS databases, load balancers, IAM, and more. Use amazon.aws collection for native AWS module support, tag everything for identification, use IAM roles instead of access keys, and combine with dynamic inventory for ongoing management. For large-scale AWS infrastructure, pair Ansible with Terraform: Terraform provisions, Ansible configures.

Related Articles

Ansible vs Terraform Complete ComparisonAnsible Dynamic Inventory Complete GuideAAP 2.6 Cloud AutomationAnsible for Docker and PodmanAnsible Performance Tuning

Category: installation

Browse all Ansible tutorials · AnsiblePilot Home