---
title: Combine Terraform and Ansible to Provision and Configure a Web Server
description: Create a functional webserver using BitOps, Terraform and Ansible
image: https://www.bitovi.com/hubfs/DevOps%20Page/Blogs/ansible-terraform.png
---

- ![AI implementation](https://www.bitovi.com/hubfs/AIConsultingIcon.svg)
  
  [AI implementation](https://www.bitovi.com/services/ai-consulting)
- ![Systems engineering](https://www.bitovi.com/hubfs/icon%20-%20backend.svg)
  
  [Systems engineering](https://www.bitovi.com/services/systems-engineering-consulting)
- ![Project Management](https://www.bitovi.com/hubfs/icon%20-%20PM.svg)
  
  [Project Management](https://www.bitovi.com/services/agile-project-management-consulting)
- ![Product Design](https://www.bitovi.com/hubfs/icon%20-%20design.svg)
  
  [Product Design](https://www.bitovi.com/services/product-design-consulting)
- ![Frontend development](https://www.bitovi.com/hubfs/icon%20-%20frontend.svg)
  
  [Frontend development](https://www.bitovi.com/services/frontend-development-consulting)
- [View more
  
  →
  
  ](https://www.bitovi.com/digital-consulting-services)

We're Experts in...

- [JavaScript](https://www.bitovi.com/services/frontend/javascript-consulting)
- [AI training](https://www.bitovi.com/ai-training-for-software-engineers)
- [Angular](https://www.bitovi.com/services/frontend/angular-consulting)
- [Design systems](https://www.bitovi.com/services/axure-figma-migration)
- [React](https://www.bitovi.com/services/frontend/react-consulting)
- [Temporal](https://www.bitovi.com/services/backend/temporal-consulting)
- [React Native](https://www.bitovi.com/services/frontend/react-consulting/react-native)
- [Node.js](https://www.bitovi.com/services/backend/nodejs-consulting)

Showcase

![Yum! Brands](https://www.bitovi.com/hubfs/yum-showcase-link-1.png)

[View case study](https://www.bitovi.com/en/bitovi-yum-case-study)

More Projects

- [![Levi's](https://www.bitovi.com/hubfs/levis.svg)](https://www.bitovi.com/web-application-consulting-work/levis-ecommerce-responsive-redesign)
- [![Christie's International Real Estate](https://www.bitovi.com/hubfs/christies.svg)](https://design.bitovi.com/christies)
- [![BAFS](https://www.bitovi.com/hubfs/bafs.svg)](https://www.bitovi.com/ux-design-consulting/ux-case-studies/bafs-ppp)
- [View more
  
  →
  
  ](https://www.bitovi.com/our-software-consulting-work)

Open Source Tools

We build powerful tools and open source them to support the community.

[See what we've built →](https://www.bitovi.com/open-source)

- [![Blog](https://www.bitovi.com/hubfs/icon%20-%20blog.svg)
  
  BlogWe post about delivering products and solving problems.
  
  ](https://www.bitovi.com/blog)
- [![Partnerships](https://www.bitovi.com/hubfs/Handshake-1.svg)
  
  PartnershipsLearn about Bitovi's technology partners
  
  ](https://www.bitovi.com/partnerships)
- [![Academy](https://www.bitovi.com/hubfs/icon%20-%20academy%20(4).svg)
  
  AcademyFree courses to build delivery skills
  
  ](https://www.bitovi.com/academy)
- [![Open source tools](https://www.bitovi.com/hubfs/icon%20-%20open%20source.svg)
  
  Open source toolsUse or contribute to our community
  
  ](https://www.bitovi.com/open-source)

Let's Connect

- [![Discord](https://www.bitovi.com/hubfs/DiscordLogo.svg)
  
  Discord
  
  ](https://discord.gg/J7ejFsZnJ4)
- [![LinkedIn](https://www.bitovi.com/hubfs/LinkedinLogo.svg)
  
  LinkedIn
  
  ](https://www.linkedin.com/company/bitovi/)
- [![GitHub](https://www.bitovi.com/hubfs/GithubLogo.svg)
  
  GitHub
  
  ](https://github.com/bitovi/)

![Eggbot](https://www.bitovi.com/hubfs/build_assets/bitovi-limbo-cms-react/338/js_client_assets/assets/eggbot-LTGhdSGL.png)

Name *

Work Email *

Phone

What's your project?

Send

### Contact Us

(312) 620-0386contact@bitovi.com

[ DevOps ](https://www.bitovi.com/blog/topic/devops) |  December 18, 2020

# Combine Terraform and Ansible to Provision and Configure a Web Server

 Create a functional webserver using BitOps, Terraform and Ansible

![Connor Graham](https://app.hubspot.com/settings/avatar/19f570d0095455dae4ba023d96feaca9)

 Connor Graham

Share:

[![Twitter](https://www.bitovi.com/hubfs/limbo-generated/_astro/twitter-white.os3xLc3C_Z2nW4or.svg) ](https://twitter.com/intent/tweet?text=) [![Reddit](https://www.bitovi.com/hubfs/limbo-generated/imgs/icons/reddit.png) ](http://reddit.com/submit?url=)

*Last Updated: December 01, 2022*

Terraform is one of the best tools for provisioning cloud infrastructure, but when working with virtual machines lacks good support for SSH'ing on to the server to configure it. This is where Ansible shines.

Passing the output of Terraform in to Ansible is not a documented process, so we'll be looking at a practical example using Terraform to create an AWS EC2 instance and configure NGINX on it with Ansible. The whole process will be orchestrated using BitOps - an open source deployment tool that bundles and runs terraform and ansible executables.

To complete this tutorial you will need

- [npm](https://nodejs.org/en/download/)
- [docker](https://docs.docker.com/get-docker/)
- An AWS account with an [aws access key and aws secret access key](https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html#access-keys-and-secret-access-keys)

If your AWS account is older than 12 months and you are outside of AWS’ [free tier](https://aws.amazon.com/free/?all-free-tier.sort-by=item.additionalFields.SortRank&all-free-tier.sort-order=asc), this tutorial will cost **$0.0104 hourly **because it creates a t3.micro EC2.

If you prefer skipping ahead to the final solution, the code created in this tutorial is on [Github](https://github.com/bitovi/bitops/tree/master/docs/examples/bitops%2Bterraform%2Bansible-blog).

## Setting up our operations repo

To start, create a fresh operations repo using yeoman.

Install yeoman and generator-bitops

```
npm install -g yo
npm install -g @bitovi/generator-bitops
```

Run `yo @bitovi/bitops` to create an operations repo. When prompted, name your application “test”, answer “Y” to Terraform and Ansible, and “N” to the other supported tools.

```
yo @bitovi/bitops
```

## ![yo-bitops-terraform-ansible](https://www.bitovi.com/hs-fs/hubfs/DevOps%20Page/Blogs/yo-bitops-terraform-ansible.png?width=550&name=yo-bitops-terraform-ansible.png)

## Managing Terraform State

Before we write any Terraform, we need to create an s3 bucket to store our [terraform state](https://www.terraform.io/docs/state/index.html) files. Fortunately, we can use the awscli installed in BitOps along with lifecycle hooks to accomplish this.

Replace `test/terraform/bitops.before-deploy.d/my-before-script.sh`, with

```
#!/bin/bash 
aws s3api create-bucket --bucket $TF_STATE_BUCKET --region $AWS_DEFAULT_REGION --create-bucket-configuration LocationConstraint=$AWS_DEFAULT_REGION || true
```

Any shell scripts in this directory will execute before any Terraform commands. This script will create a s3 bucket with the name of whatever we set TF_STATE_BUCKET to.

We will need to pass in TF_STATE_BUCKET when running BitOps. S3 bucket names need to be globally unique, so don’t use the same name outlined in this tutorial.

## Customize terraform to create our server

Replace the contents of `test/terraform/main.tf` with the following. Terraform does not support variable interpolation in `backend` blocks, so we’ll have to hardcode our backend state config.

Be sure to replace YOUR_BUCKET_NAME with the name you want to use for your state bucket.

```
terraform {
 required_providers {
   aws = {
     source  = "hashicorp/aws"
     version = "~> 3.0"
   }
 }
 backend "s3" {
   bucket = "YOUR_BUCKET_NAME"
   key    = "state"
 }
}
 
data "aws_region" "current" {}
 
resource "aws_vpc" "main" {
 cidr_block = "10.0.0.0/16"
}
 
resource "aws_internet_gateway" "gw" {
 vpc_id = aws_vpc.main.id
}
 
resource "aws_subnet" "main" {
 vpc_id            = aws_vpc.main.id
 cidr_block        = aws_vpc.main.cidr_block
 availability_zone = "${data.aws_region.current.name}a"
}
 
resource "aws_route_table" "rt" {
 vpc_id = aws_vpc.main.id
 route {
   cidr_block = "0.0.0.0/0"
   gateway_id = aws_internet_gateway.gw.id
 }
}
 
resource "aws_route_table_association" "route_table_association" {
 subnet_id      = aws_subnet.main.id
 route_table_id = aws_route_table.rt.id
}
 
data "aws_ami" "ubuntu" {
 most_recent = true
 filter {
   name   = "name"
   values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
 }
 filter {
   name   = "virtualization-type"
   values = ["hvm"]
 }
 owners = ["099720109477"]
}
 
resource "tls_private_key" "key" {
 algorithm = "RSA"
 rsa_bits  = 4096
}
 
resource "aws_key_pair" "aws_key" {
 key_name   = "bitops-ssh-key"
 public_key = tls_private_key.key.public_key_openssh
}
 
resource "aws_security_group" "allow_http" {
 name        = "allow_http"
 description = "Allow HTTP traffic"
 vpc_id      = aws_vpc.main.id
 ingress {
   description = "HTTP"
   from_port   = 80
   to_port     = 80
   protocol    = "tcp"
   cidr_blocks = ["0.0.0.0/0"]
 }
 egress {
   from_port   = 0
   to_port     = 0
   protocol    = "-1"
   cidr_blocks = ["0.0.0.0/0"]
 }
}
 
resource "aws_security_group" "allow_ssh" {
 name        = "allow_ssh"
 description = "Allow SSH traffic"
 vpc_id      = aws_vpc.main.id
 ingress {
   description = "SSHC"
   from_port   = 22
   to_port     = 22
   protocol    = "tcp"
   cidr_blocks = ["0.0.0.0/0"]
 }
 egress {
   from_port   = 0
   to_port     = 0
   protocol    = "-1"
   cidr_blocks = ["0.0.0.0/0"]
 }
}
 
resource "aws_instance" "server" {
 ami                         = data.aws_ami.ubuntu.id
 instance_type               = "t3.micro"
 key_name                    = aws_key_pair.aws_key.key_name
 associate_public_ip_address = true
 subnet_id                   = aws_subnet.main.id
 vpc_security_group_ids      = [aws_security_group.allow_http.id, aws_security_group.allow_ssh.id]
 
 tags = {
   Name = "BitOps test instance"
 }
}
```

The above HCL will create

- A new [VPC](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc), [subnet](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/subnet), [internet gateway](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/internet_gateway) and [route table](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table)
- A new AWS t3.micro [ec2](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance) called “BitOps test instance” with a public ip address and port 22 (ssh) and 80 (http) [accessible](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group)
- A [ssh key](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/key_pair) for accessing the instance

## Run BitOps

Within the yo generated README, there will be a command to run BitOps. Be sure to set `AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY`, `AWS_DEFAULT_REGION` and `TF_STATE_BUCKET` or BitOps will return an error.

Running BitOps will

- Configure access to AWS
- Auto-detect terraform code within the `test` environment/directory
- Run any scripts defined in `test/terraform/bitops.before-deploy.d/`
- Run `terraform init`, `terraform plan`, and `terraform apply`
- Run any scripts defined in `test/terraform/bitops.after-deploy.d/`
- Auto-detect ansible code within the `test` environment/directory
- Run any scripts defined in `test/ansible/bitops.before-deploy.d/`
- Run `ansible-playbook` on all `yml` files in `test/ansible/`
- Run any scripts defined in `test/ansible/bitops.after-deploy.d/`

```
export AWS_ACCESS_KEY_ID=YOUR_AWS_ACCESS_KEY
export AWS_SECRET_ACCESS_KEY=YOUR_AWS_SECRET_ACCESS_KEY
export AWS_DEFAULT_REGION="us-east-1"
export TF_STATE_BUCKET="my-bitops-bucket"
docker run \
-e BITOPS_ENVIRONMENT="test" \
-e AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID \
-e AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY \
-e AWS_DEFAULT_REGION=$AWS_DEFAULT_REGION \
-e TF_STATE_BUCKET=$TF_STATE_BUCKET \
-e TERRAFORM_APPLY=true \
-v $(pwd):/opt/bitops_deployment \
bitovi/bitops:latest
```

Run the above commands then check AWS Services > EC2 and you should see your newly created BitOps test instance!

![bitops+terraform+ansible-AWS](https://www.bitovi.com/hs-fs/hubfs/DevOps%20Page/Blogs/bitops+terraform+ansible-AWS.png?width=1758&name=bitops+terraform+ansible-AWS.png)

## Passing information to Ansible

Ansible requires an inventory file to work against. This cannot be a static file because it needs to contain the ip address and ssh key created by Terraform. 

Within `test/terraform/` create two new files `inventory.tmpl` and `inventory.tf`.

###### inventory.tmpl

```
bitops_servers:
 hosts:
   ${ip} 
 vars:
   ansible_ssh_user: ubuntu
   ansible_ssh_private_key_file: ${ssh_keyfile}
```

###### inventory.tf

```
resource "local_file" "private_key" {
  sensitive_content = tls_private_key.key.private_key_pem
  filename          = format("%s/%s/%s", abspath(path.root), ".ssh", "bitops-ssh-key.pem")
  file_permission   = "0600"
}
resource "local_file" "ansible_inventory" {
  content = templatefile("inventory.tmpl", {
      ip          = aws_instance.server.public_ip,
      ssh_keyfile = local_file.private_key.filename
  })
  filename = format("%s/%s", abspath(path.root), "inventory.yaml")
}
```

This HCL will save the private key registered with the EC2 instance to a local file within the docker container. It will also create a local file called `inventory.yaml` containing the ip address of the new instance as well as an absolute path to the private key file.

inventory.yaml will look something like this:

```
bitops_servers:
 hosts:
   123.123.123.123
 vars:
   ansible_ssh_user: ubuntu
   ansible_ssh_private_key_file: /home/users/connor/test/terraform/.ssh/bitops-ssh-key.pem
```

Now Ansible needs to be updated to use this `inventory.yaml` file.

## Reading inventory.yaml

Delete `test/ansible/inventory.yml` and replace the contents of `test/ansible/ansible.cfg` with

```
[defaults]
inventory=../terraform/inventory.yaml
host_key_checking = False
```

This will tell Ansible to read the `inventory.yaml` file created by Terraform to connect to our new EC2 instance.

## Customizing Ansible

Now that Ansible has access to the instance, customize `test/ansible/playbook.yaml` to install NGINX. Replace the contents of `playbook.yaml` with

```
- hosts: bitops_servers
  tasks:
    - name: ensure nginx is at the latest version
      apt: name=nginx state=latest
      become: yes
    
    - name: start nginx
      service:
        name: nginx
        state: started
```

## Run BitOps Again

At this point, your operations repository should look like this:

**![bitops+terraform+ansible-finalrepo](https://www.bitovi.com/hs-fs/hubfs/DevOps%20Page/Blogs/bitops+terraform+ansible-finalrepo.png?width=500&name=bitops+terraform+ansible-finalrepo.png)**

Run BitOps once more to create `inventory.yaml` and install NGINX

```
export AWS_ACCESS_KEY_ID=YOUR_AWS_ACCESS_KEY
export AWS_SECRET_ACCESS_KEY=YOUR_AWS_SECRET_ACCESS_KEY
export AWS_DEFAULT_REGION=”us-east-1”
export TF_STATE_BUCKET=”my-bitops-bucket”
docker run \
-e BITOPS_ENVIRONMENT="test" \
-e AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID \
-e AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY \
-e AWS_DEFAULT_REGION=$AWS_DEFAULT_REGION \
-e TF_STATE_BUCKET=$TF_STATE_BUCKET \
-e TERRAFORM_APPLY=true \
-v $(pwd):/opt/bitops_deployment \
bitovi/bitops:latest
```

## Verify

Go to the AWS console and look up the public ip address of your ec2 instance, copy and paste this into your browser and you should see the default nginx landing page!

![bitops+terraform+ansible-nginx](https://www.bitovi.com/hs-fs/hubfs/DevOps%20Page/Blogs/bitops+terraform+ansible-nginx.png?width=1860&name=bitops+terraform+ansible-nginx.png)

## Cleanup

Run BitOps again, but pass in the environment variable `TERRAFORM_DESTROY` with the value `true`. This will force BitOps to run `terraform destroy` and delete all the infrastructure BitOps created.

```
docker run \
-e BITOPS_ENVIRONMENT="test" \
-e AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID \
-e AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY \
-e AWS_DEFAULT_REGION=$AWS_DEFAULT_REGION \
-e TERRAFORM_DESTROY=true \
-e ANSIBLE_SKIP_DEPLOY=true \
-e TF_STATE_BUCKET=$TF_STATE_BUCKET \
-v $(pwd):/opt/bitops_deployment
```

Don’t forget to also delete your S3 bucket when you’re done!

See the [docs](https://bitovi.github.io/bitops/tool-configuration/configuration-terraform) for more information

## Learn More

In this article, we’ve created an NGINX enabled EC2 instance using BitOps, Terraform and Ansible.

Want to learn more about using BitOps? Check out our [github](https://github.com/bitovi/bitops), our [official docs,](https://bitovi.github.io/bitops/) or come hang out with us on [Slack #bitops channel](https://bitovi-community.slack.com/archives/C01SX5L134G)<https://www.bitovi.com/community/slack>! We’re happy to assist you at any time in your [DevOps](https://www.bitovi.com/blog/the-complete-guide-to-devops) automation journey!

[![Tag for open source](https://www.bitovi.com/hubfs/limbo/icons/tag.svg) open source ](https://www.bitovi.com/blog/topic/open-source)[![Tag for development](https://www.bitovi.com/hubfs/limbo/icons/tag.svg) development ](https://www.bitovi.com/blog/topic/development)[![Tag for bitops](https://www.bitovi.com/hubfs/limbo/icons/tag.svg) bitops ](https://www.bitovi.com/blog/topic/bitops)

 Previous Post

![](https://www.bitovi.com/hs-fs/hubfs/blogposts/event-driven-automation-stackstorm/stackstorm-logo-social-media-tag.png?height=117&name=stackstorm-logo-social-media-tag.png) [ DevOps Automation using StackStorm - Getting Started Guide ](https://www.bitovi.com/blog/devops-automation-using-stackstorm-getting-started)

  

 Next Post

![](https://lh3.googleusercontent.com/q_VM75EIgttXruptl8VWIYXjuSrHYVmFZl8FFDt8xFv-xEqGCZi3UToh3ufqPz_eMnjc4DOecB9yaiExw4ZjMTAJFqzeZnasXaz-Tsto9cPG0WhwNkTWOejgP9-7aDAKZ3MijTKw) [ 8 Topics Every Node.js Microservice Developer Should Know ](https://www.bitovi.com/blog/8-topics-every-nodejs-microservice-developer-should-know)

```json
{
  "@context" : "http://schema.org",
  "@type" : "Organization",
  "address" : {
    "@type" : "PostalAddress",
    "addressCountry" : "United States",
    "addressLocality" : "Libertyville",
    "addressRegion" : "IL",
    "postalCode" : "60048",
    "streetAddress" : "1134 Pine Tree Lane "
  },
  "alternateName" : "Bitovi",
  "areaServed" : {
    "@type" : "GeoCircle",
    "geoMidpoint" : {
      "@type" : "GeoCoordinates",
      "latitude" : "41.8781",
      "longitude" : "87.6298"
    },
    "geoRadius" : "5000 km"
  },
  "description" : "Bitovi is a UX, UI design and front-end JavaScript development consulting company",
  "email" : "contact@bitovi.com",
  "image" : "https://www.bitovi.com/hubfs/bitovi-logo-x2.png",
  "logo" : "https://www.bitovi.com/hubfs/bitovi-logo-23-1.svg",
  "mainEntityOfPage" : {
    "@id" : "https://www.bitovi.com/blog/bitops-terraform-ansible",
    "@type" : "WebPage",
    "description" : "Create a functional webserver using BitOps, Terraform and Ansible"
  },
  "naics" : "541511",
  "name" : "Bitovi Web App Consulting",
  "sameAs" : [ "https://www.facebook.com/BitoviLLC/", "https://twitter.com/bitovi", "https://www.linkedin.com/company/bitovi" ],
  "telephone" : "312-620-0386",
  "url" : "http://bitovi.com"
}
```

```json
{
  "@context" : "http://schema.org",
  "@type" : "BlogPosting",
  "author" : {
    "@type" : "Person",
    "name" : "Connor Graham"
  },
  "dateModified" : "December 6, 2022, 8:56:22 PM",
  "datePublished" : "2020-12-18 15:11:20",
  "description" : "Create a functional webserver using BitOps, Terraform and Ansible",
  "headline" : "Combine Terraform and Ansible to Provision and Configure a Web Server",
  "image" : {
    "@type" : "ImageObject",
    "url" : "https://www.bitovi.com/hubfs/DevOps%20Page/Blogs/ansible-terraform.png"
  },
  "publisher" : {
    "@type" : "Organization",
    "logo" : {
      "@type" : "ImageObject",
      "url" : "https://www.bitovi.com/hubfs/bitovi-logo-23-1.svg"
    },
    "name" : "Bitovi"
  }
}
```