Home
Blog
Ansible Tutorial: Automating EC2 Configuration with Terraform, Docker, and Jenkins

Ansible Tutorial: Automating EC2 Configuration with Terraform, Docker, and Jenkins

Ansible Tutorial: Automating EC2 Configuration with Terraform, Docker, and Jenkins
with special guest
Mitchell
Hashimoto
Mitchell Hashimoto headshot

Infrastructure automation has become an essential part of modern DevOps and platform engineering. While Terraform is excellent for provisioning infrastructure, tools like Ansible help automate software installation, configuration management, and application deployment.

In this tutorial, we'll walk through a practical example that combines Terraform and Ansible to create an AWS EC2 instance, install Docker, and deploy Jenkins inside a Docker container. We'll also explore how the same workflow can be managed using N0 (nZero) templates for a more streamlined deployment process.

If you'd like to watch the complete hands-on demonstration and follow along step-by-step, be sure to check out the full video here:

Why Use Ansible?

Ansible is a powerful configuration management and automation tool that enables engineers to automate repetitive tasks across servers and cloud environments.

Key benefits of Ansible include:

  • Agentless architecture
  • Easy-to-read YAML playbooks
  • Infrastructure automation
  • Configuration management
  • Application deployment
  • Idempotent execution (safe to run multiple times)

When paired with Terraform, Ansible creates a complete Infrastructure-as-Code (IaC) workflow where Terraform provisions resources and Ansible configures them.

Step 1: Provision an EC2 Instance Using Terraform

The first step is creating the AWS infrastructure.

Navigate to the Terraform directory and initialize the project:

cd terraform

terraform init

Apply the Terraform configuration:

terraform apply -auto-approve

For demonstration purposes, the -auto-approve flag is used. In production environments, manual approval is recommended.

Terraform creates several AWS resources, including:

  • Virtual Private Cloud (VPC)
  • TLS Private Key
  • AWS Subnet
  • Security Group
  • Route Table
  • Route Table Association
  • Internet Gateway
  • EC2 Instance
  • AWS Key Pair
  • Elastic IP Address

Once deployment is complete, Terraform outputs:

  • Private SSH Key
  • Public IP Address
  • Public DNS Name

These outputs will be used by Ansible to connect to the server.

Step 2: Configure the Ansible Inventory File

Ansible requires an inventory file that defines target hosts.

Example inventory structure:

Ansible Inventory for Jenkins Host

This inventory file defines a Jenkins host group containing a single VM and specifies connection settings that Ansible will use when executing playbooks.


[jenkins]
jenkins-vm ansible_host=

[jenkins:vars]
ansible_user=ubuntu
ansible_python_interpreter=/usr/bin/python3

What Each Setting Does

jenkins Inventory group containing Jenkins servers.
jenkins-vm Logical hostname used by Ansible.
ansible_host Actual public IP or DNS name of the VM.
ansible_user SSH username used for remote access.
ansible_python_interpreter Python executable used by Ansible on the target host.

Example Connectivity Test


ansible jenkins -i inventory.ini -m ping

A successful response should return pong, confirming that Ansible can SSH into the Jenkins VM and execute Python-based modules.

Instead of manually inserting the EC2 IP address, Terraform output can automatically populate the inventory file.

This creates a dynamic workflow where newly provisioned infrastructure becomes immediately available to Ansible.

Step 3: Export the SSH Private Key

Ansible uses SSH to connect to the EC2 instance.

Export the Terraform-generated private key:

Export Terraform SSH Private Key

If your Terraform configuration exposes an SSH private key as an output, you can write it to a PEM file and then secure the file with restrictive permissions.


# Export the private key output to a PEM file
terraform output -raw private_key > key/mykey.pem

# Restrict file permissions
chmod 400 key/mykey.pem

Why chmod 400?

Owner Read only
Group No access
Others No access

OpenSSH refuses to use private keys that are accessible by other users. Setting permissions to 400 ensures only the file owner can read the key.

Example SSH Connection


ssh -i key/mykey.pem ubuntu@

Replace with the IP address of the provisioned instance.

Proper permissions are required for successful SSH authentication.

Step 4: Execute the Ansible Playbook

Move into the Ansible directory:

Run an Ansible Playbook

After generating your SSH key and configuring your inventory, navigate to the Ansible directory and execute the playbook using the private key created by Terraform.


cd ansible

ansible-playbook \
--private-key ../key/mykey.pem \
-i inventory \
playbook.yml

Command Breakdown

cd ansible Move into the Ansible project directory.
--private-key SSH private key used to connect to target hosts.
-i inventory Inventory file containing target hosts.
playbook.yml Playbook defining the automation tasks to execute.

Optional Connectivity Test


ansible all \
--private-key ../key/mykey.pem \
-i inventory \
-m ping

A successful ping returns pong, confirming SSH connectivity and Ansible access before running the full playbook.

This command instructs Ansible to:

  • Use the specified SSH key
  • Read hosts from the inventory file
  • Execute the defined playbook

Understanding the Ansible Playbook

The playbook targets all hosts in the inventory:

Ansible Play-Level Privilege Escalation

The following playbook settings instruct Ansible to target all hosts in the inventory and execute tasks with elevated privileges.


hosts: all
become: true

What These Settings Mean

hosts: all Run the play against every host defined in the selected inventory.
become: true Enable privilege escalation (typically via sudo) so tasks can perform administrative operations.

Why Use become: true?

  • Install operating system packages
  • Manage system services
  • Modify files under /etc
  • Create users and groups
  • Configure firewalls and networking
  • Manage Docker, Jenkins, Nginx, and other system software

Example


- hosts: all
become: true

tasks:
- name: Install Git
apt:
name: git
state: present
update_cache: true

Without become: true, tasks that require administrative permissions may fail with "permission denied" or package-management errors.

Task 1: Install Required Packages

The first task installs:

  • Python Pip
  • Unzip

Example:

- name: Install required packages apt: update_cache: yes name: - python3-pip - unzip state: present

Retry logic is included to handle temporary APT lock issues commonly encountered on Ubuntu systems.

Task 2: Add Docker Repository

Before Docker can be installed, Ansible adds Docker's official GPG key and repository.

This ensures the latest Docker packages are available through APT.

Task 3: Install Docker CE

Docker Community Edition is installed:

- name: Install Docker Engine apt: name: docker-ce state: latest

Docker becomes the runtime environment for Jenkins.

Task 4: Install Docker Python Module

Ansible interacts with Docker through Python modules.

- name: Install Docker Python SDK pip: name: docker

This allows Ansible's Docker modules to manage containers directly.

Task 5: Pull the Jenkins Docker Image

Next, Ansible downloads the Jenkins image:

- name: Pull Jenkins Docker image community.docker.docker_image: name: jenkins/jenkins source: pull

This ensures the latest Jenkins image is available locally.

Task 6: Configure Jenkins Data Directory

Persistent storage is critical for Jenkins.

Ansible sets ownership and permissions:

owner: ubuntugroup: ubunturecurse: yes

This prevents permission-related issues when Jenkins writes data.

Task 7: Deploy the Jenkins Container

Finally, Ansible launches Jenkins:

- name: Run Jenkins container community.docker.docker_container: name: jenkins image: jenkins/jenkins state: started restart_policy: unless-stopped ports: - "8080:8080" - "50000:50000" volumes: - "/home/ubuntu/jenkins_data:/var/jenkins_home"

This configuration ensures Jenkins data persists even if the container is recreated.

Ansible Idempotency Explained

One of Ansible's most valuable features is idempotency.

When the playbook is executed a second time:

ansible-playbook -i inventory playbook.yml

Ansible checks whether each task has already been completed.

If everything is already configured correctly, the output shows:

changed=0

failed=0

This guarantees safe and repeatable deployments.

Accessing Jenkins

Once deployment completes, open:

Accessing Jenkins

Once the Jenkins container is running and the EC2 security group allows inbound traffic on port 8080, you can access the Jenkins web interface using:


http://:8080

Example


http://54.210.123.45:8080

Verify Security Group Rules

Ensure your EC2 instance allows inbound TCP traffic on port 8080:


Type: Custom TCP
Protocol: TCP
Port Range: 8080
Source: Your IP (recommended)

First Login

Jenkins will display an "Unlock Jenkins" screen. Retrieve the initial administrator password:


docker exec jenkins \
cat /var/jenkins_home/secrets/initialAdminPassword

Copy the password, paste it into the Jenkins setup page, and continue with the installation wizard.

Jenkins displays the initial setup screen requesting an administrator password.

Retrieving the Jenkins Initial Password

SSH into the EC2 instance:

Retrieve the Jenkins Initial Admin Password

If Jenkins data is persisted on the host using a volume mount, you can SSH into the server and read the initial administrator password directly from the Jenkins secrets directory.


# Connect to the server
ssh -i mykey.pem ubuntu@

# Navigate to Jenkins secrets
cd ~/jenkins_data/secrets

# Display the initial admin password
cat initialAdminPassword

Example Output


d5a4b5b7d2c34f9ea123456789abcdef

Alternative: Read Directly from the Container


docker exec jenkins \
cat /var/jenkins_home/secrets/initialAdminPassword

Use the Password

Open the Jenkins web UI:


http://:8080

Paste the retrieved password into the "Unlock Jenkins" screen and continue with the setup wizard.

Copy the password and paste it into the Jenkins setup wizard.

You can then:

  • Install recommended plugins
  • Skip plugin installation
  • Create administrator accounts
  • Complete Jenkins setup

Jenkins is now fully operational.

Managing Ansible Through N0 (nZero)

The tutorial also demonstrates deploying the same Ansible workflow using N0 templates.

Creating an Ansible Template

The template includes:

  • Template Name
  • Ansible Version
  • SSH Private Key
  • GitHub Repository
  • Playbook Location
  • Environment Variables

Example inventory variable:

export ANSIBLE_INVENTORY=inventory

This allows N0 to locate the correct inventory file.

Running Deployments in N0

Once the template is created:

  1. Create a new environment.
  2. Select the Ansible template.
  3. Launch deployment.
  4. Review logs.
  5. Approve the run.

N0 automatically:

  • Clones the repository
  • Loads variables
  • Executes the Ansible playbook
  • Displays deployment logs

Because the EC2 instance was already configured through the CLI, the deployment shows no additional changes.

Building a Complete Terraform + Ansible Workflow

One of the most powerful capabilities demonstrated is combining Terraform and Ansible into a single automated workflow.

A typical process would be:

Stage 1: Terraform

  • Create VPC
  • Create Subnets
  • Create Security Groups
  • Launch EC2 Instances

Stage 2: Ansible

  • Install packages
  • Configure Docker
  • Deploy Jenkins
  • Configure applications

This eliminates manual intervention and creates a fully automated infrastructure pipeline.

Benefits of Combining Terraform and Ansible

Terraform Strengths

  • Infrastructure provisioning
  • Cloud resource management
  • State management
  • Multi-cloud support

Ansible Strengths

  • Configuration management
  • Software installation
  • Application deployment
  • Server automation

Together they provide a complete Infrastructure-as-Code solution.

Conclusion

This hands-on example demonstrates how Terraform and Ansible complement each other in modern DevOps workflows. Terraform provisions AWS infrastructure while Ansible handles software installation and configuration.

Using a simple Ansible playbook, we successfully:

  • Provisioned an AWS EC2 instance
  • Installed Docker
  • Pulled a Jenkins Docker image
  • Configured persistent storage
  • Deployed a Jenkins container
  • Verified idempotent execution
  • Managed deployments through N0 templates

For platform engineers, DevOps practitioners, and cloud architects, mastering the combination of Terraform and Ansible is a valuable skill that enables scalable, repeatable, and automated infrastructure deployments.

Schedule a technical demo
See env zero in action
Schedule demo

Related Content

All articles