Terraform Guide - Docker Containers & AWS ECR(elastic container registry)?
Terraform is the most popular IaC(Infrastructure as Code) tool for provisioning your cloud infrastructure on AWS, GCP and Azure.
Terraform capabilities are not limited to cloud infrastructure instead you can use Terraform for other container infrastructure provisioning also. In this blog post, we will take a look at "How to Use Terraform for provisioning(setup/start/stop) of Docker container?"
Objective-
- Setup Application- Setup a simple server application using Node.js
- Create Dockerfile- Create Dockerfile for Node.js application
- Build Docker Image- Build Docker Image of Node.js application
- Test Docker Image- Start, Run and Stop Node.js docker container using Docker commands
- Create Terraform Manifest for Docker- Create Terraform Manifest for the Node.js Docker image
- Start and Stop Docker image using Terraform- Finally we will only use terraform init, terraform plan and terraform apply command to start and stop the Docker container of our Node.js application.
- How to Push Docker Image to ECR(elastic container registry) on AWS- A detailed instructions on how to push Docker image to ECR
- Conclusion
Prerequisites
Here are the prerequisites for this blog post. Since this is a little advanced topic of Terraform where you need to have the following software component already installed onto your laptop/desktop -
-
Docker- As this article is focused on Docker, so you must have Docker Desktop and [Docker engine] 22 installed onto your working laptop/desktop. Here are the installation guide provided by docker based on the operating system -
Docker Desktop & Docker Engine
- MacOS - Click here to see the installation instruction
- macOS Silicon chip - Click here to see the installation instruction
- Windows - Click here to see the installation instruction
- Linux Click here to see the installation instruction
-
Terraform- The second important software package you need to have is Terraform installed. Here is a a detailed guide on installation of Terraform based on the different operating systems (macOS, MacOS Silicon chip, Windows, Linux) -
- Install Terraform - Click here to see the installation instruction
-
NodeJS- The third component which you need is NodeJS because we will set up a very basic NodeJS server application.
- Install NodeJS - Following these installation instructions
-
IDE - At last you need a good IDE(Integrated Development environment). Any of the following would work -
1. Create a Node.js app
Alright now we are starting from scratch so let's set up a very basic Node.js application for this tutorial.
1. package.json- Create a directory named terrafrom-docker
and inside that directory create a file package. json
. This file will be used for putting the required dependencies and also the information about the app -
1 <!-- package.json-->
2
3 {
4 "name": "docker_node_js_web_app",
5 "version": "1.0.0",
6 "description": "Node.js on Docker manged by Terrafrom",
7 "author": "Rahul Wagh <rahul.wagh@jhooq.com>",
8 "main": "server.js",
9 "scripts": {
10 "start": "node server.js"
11 },
12 "dependencies": {
13 "express": "^4.16.1"
14 }
15}
2. server.js- Now we will create server.js
in which we will write a very basic HTTP server code running on
port 8080.
1<!-- server.js-->
2
3 'use strict';
4
5 const express = require('express');
6
7 // Constants
8 const PORT = 8080;
9 const HOST = '0.0.0.0';
10
11 // App
12 const app = express();
13 app.get('/', (req, res) => {
14 res.send('Hello World');
15 });
16
17 app.listen(PORT, HOST);
18 console.log(`Running on http://${HOST}:${PORT}`);
3.npm install- After creating package.json
and server.js
now we need to run $ npm install
command. The
npm install command will generate the package-lock.json file and in the later steps we will use the same inside
the Dockerfile
1# run the following command /terraform-docker
2
3 npm install
You should see the following output at your console -
Here is the directory structure of the project -
4. Verify Node.js application- Now we have successfully installed all the node js dependencies let's first verify the application -
Run the following command to start the node js application-
1# following command will start the node.js application. In the next step, we will create a docker file
2
3 node server.js
Verify the node.js application with the help of curl
command -
1curl localhost:8080
Now our node js application is ready and we need to create a Docker file for the application.
2. Create Dockerfile
In the previous step we have seen how to create a very simple Node JS application running on port 8080. Now we will create a Dockerfile, so that we wrap the application and run it inside a Docker container.
Here is the Dockerfile
which you need to create and save inside your project -
1FROM node:16
2
3# Create an app directory
4WORKDIR /usr/src/app
5
6# Install app dependencies
7# A wildcard is used to ensure both package.json AND package-lock.json are copied
8# where available (npm@5+)
9COPY package*.json ./
10
11RUN npm install
12# If you are building your code for production
13# RUN npm ci --only=production
14
15# Bundle app source
16COPY . .
17
18EXPOSE 8080
19CMD [ "node", "server.js" ]
3. Build Docker Image by Tagging it with a suitable name
Now we have Node JS application ready and we also have Dockerfile ready with us.
Let's run some docker command to create a docker image -
1docker build . -t ubuntu/docker-node-terraform-app
docker build-
Here is the docker build output which you should see once you build and tag the image -
List docker image-
After building and tagging the image let's verify the docker image by running the command $ docker images ls
1docker image ls
Here is the output after building the image-
4. Test Docker Image
Now we are at a point where we can run and verify the docker image for the Node JS application which we have built.
To verify the docker image, we need to start the docker container, and to start the docker container we need the following info -
- Docker image name - ubuntu/docker-node-terraform-app
- Port on Local machine - 8080
- Port on host machine - 49160
Here is the command to run and test the docker image -
1docker run -p 49160:8080 -d ubuntu/docker-node-terraform-app
After running the docker image you should follow output where it would spit out the docker container id for you-
As we have started the docker container on the port 41960 on the local machine so we should be able to access it by curl
command -
1curl localhost:41960
Here is the screenshot-
So till now, we have finished -
- Setting up Node JS application
- Creating Dockerfile
- Running and verifying Docker container
In the next step, we will create a Terraform Configuration for the Docker container.
5. Create Terraform Manifest for Docker
All the previous steps are pretty much prerequisites and now we are ready to write the Terraform configuration to provision/start the docker container.
5.1 Define Docker provider
As we are working with Docker so the provider should be docker -
1## Step 1 - Define provider
2
3terraform {
4 required_providers {
5 docker = {
6 source = "kreuzwerker/docker"
7 version = "2.21.0"
8 }
9 }
10}
5.2 Pull the docker image
Now we need to provide a reference to the Docker Image(ubuntu/docker-node-terraform-app) which we have just built -
1## Step 2 - Docker image reference so that it can pull the local or remote docker image
2
3resource "docker_image" "node-js-app" {
4 name = "ubuntu/docker-node-terraform-app"
5 keep_locally = false
6}
5.3 Define the docker ports for running the application
In the final part of terraforming configuration we are going to mention the internal as well as external port references -
1## Step 3 - Mention the ports for localhost as well as for external host
2
3resource "docker_container" "foo" {
4 image = docker_image.node-js-app.latest
5 name = "tutorial"
6 ports {
7 internal = 8080 ## Host machine
8 external = 49160 ## Local machine
9 }
10}
5.4 Complete terraform code -
1 terraform {
2 required_providers {
3 docker = {
4 source = "kreuzwerker/docker"
5 version = "2.21.0"
6 }
7 }
8}
9
10provider "docker" {
11}
12
13# Pulls the image
14resource "docker_image" "node-js-app" {
15 name = "ubuntu/docker-node-terraform-app"
16 keep_locally = false
17}
18
19
20resource "docker_container" "foo" {
21 image = docker_image.node-js-app.latest
22 name = "tutorial"
23 ports {
24 internal = 8080
25 external = 49160
26 }
27}
6. Start and Stop Docker image using Terraform
6.1 Run terraform init command
Alright now we have everything ready with us and it is time for us to run the first $ terraform init
command.
Go to your project directory and run the following command -
1terraform init
After running the command you should see a successful message stating - "Terraform has been successfully initialized"
6.2 Run terraform plan command
After running the terraform init command successfully the next command we are going to run is $ terraform plan
.
Here is the command -
1terrafrom plan
After running the command you should observe the following messages on your console where it says -
- "docker_image.node-js-app will be created"
- Plan: 2 to add, 0 to change, 0 to destroy
Run terraform plan command
6.3 Run terraform apply command
Finally, you need to run $ terraform apply
command.
Here is the command -
1terraform apply
$ terraform apply
command you should following message onto your console -
- "docker_image.node-js-app will be created"
- "Docker container ID"
Run terraform apply command to start docker container
You can also do a final verification by listing all the running docker container $ docker ps
1docker ps
Here is the output you should see -
6.4 Verify the application by using the curl
At last, you can verify the application with the help of curl command -
1 curl localhost:41960
7. How to Push Docker Image to ECR(elastic container registry) on AWS
Terraform is great tool for provisioning the infrastructure but when it comes to pushing docker image to AWS ECR(elastic container registry) then I would say Terraform is not recommended for performing operations such as docker push
or docker pull
Although you can achieve the desired functionality of docker push, docker pull
with the help of Terrafrom local-exec provisioner. But still it would be bad practice for manging the container infrastructure.
In this section we will take a look on both Recommended as well as Not Recommended ways of pushing docker image to ECR -
7.1 Recommended way - Use Terraform to create ECR(elastic container registry) and then use docker command separately to push images -
As I have mentioned, Terraform is not recommended for performing the docker pull, docker push
operation but instead you can use the Terraform in the following ways to improve your container infrastructure provisioning -
1. Authenticate to AWS ECR- The first step would be to authenticate yourself with AWS ECR using the following commands_(Note- Please do change the repository URI based on your )_
1aws ecr get-login-password --region aws_region | \
2docker login --username AWS --password-stdin account_id.dkr.ecr.us-east-1.amazonaws.com
2. Create ECR Repository using Terraform- Use the following Terraform Code where we will use the "aws_ecr_repository" and "aws_ecr_repository_policy" for creating the ECR repository.
1resource "aws_ecr_repository" "docker_ecr_repo" {
2 name = "docker_ecr_repo"
3 image_tag_mutability = "IMMUTABLE"
4}
5
6resource "aws_ecr_repository_policy" "demo-repo-policy" {
7 repository = aws_ecr_repository.docker_ecr_repo.name
8 policy = <<EOF
9 {
10 "Version": "2008-10-17",
11 "Statement": [
12 {
13 "Sid": "Set the permission for ECR",
14 "Effect": "Allow",
15 "Principal": "*",
16 "Action": [
17 "ecr:BatchCheckLayerAvailability",
18 "ecr:BatchGetImage",
19 "ecr:CompleteLayerUpload",
20 "ecr:GetDownloadUrlForLayer",
21 "ecr:GetLifecyclePolicy",
22 "ecr:InitiateLayerUpload",
23 "ecr:PutImage",
24 "ecr:UploadLayerPart"
25 ]
26 }
27 ]
28 }
29 EOF
30}
3. Push Docker Image to ECR- After creating the ECR repository use the following docker command to build and push the image -
1# Step-1: Tag docker image.
2# Note: Replace the repositoryURI based on the ECR repository which you have created
3docker tag my-image repositoryURI/repository-name:myimage
4
5# Step-2: Push Docker image
6docker push repositoryURI/repository-name:myimage
7.2 Not a recommended way to push docker image to AWS's ECR(elastic container registry)
Here is how you need to implement local-exec provisioner -
-
Create a bash file(push_docker_image)- Write down all the commands related to aws ecr login as well as docker login and docker push
1# Step-1: AWS Authentication so that you can push the docker image later 2 3aws ecr get-login-password --region aws_region | \ 4docker login --username AWS --password-stdin account_id.dkr.ecr.us-east-1.amazonaws.com 5 6# Step-2: Tag docker image. 7# Note: Replace the repositoryURI based on the ECR repository which you have created 8docker tag my-image repositoryURI/repository-name:myimage 9 10# Step-3: Push Docker image 11docker push repositoryURI/repository-name:myimage
-
Use local-exec provisioner- Now need to write the Terraform file
main.tf
and need to implement local-exec provisioner so that we can execute the bash script containing docker push commands -1provider "aws" { 2 region = "eu-central-1" 3 access_key = "<YOUR_ACCESS_KEY>" 4 secret_key = "<YOUR_SECRET_KEY>" 5 6} 7 8resource "aws_instance" "ec2_example" { 9 10 ami = "ami-0767046d1677be5a0" 11 instance_type = "t2.micro" 12 key_name= "aws_key" 13 vpc_security_group_ids = [aws_security_group.main.id] 14 15 provisioner "file" { 16 # Name of the bash file .i.e. push_docker_image 17 source = "push_docker_image" 18 19 # Update the destination where you want to put your bash file 20 destination = "/home/ubuntu/push_docker_image" 21 } 22 23 provisioner "remote-exec" { 24 inline = [ 25 26 # Change the mode of bash file to make it executable 27 "chmod +x push_docker_image", 28 29 "/home/ubuntu/push_docker_image", 30 ] 31 } 32 connection { 33 type = "ssh" 34 host = self.public_ip 35 user = "ubuntu" 36 private_key = file("/home/rahul/Jhooq/keys/aws/aws_key") 37 timeout = "4m" 38 } 39} 40 41resource "aws_security_group" "main" { 42 egress = [ 43 { 44 cidr_blocks = [ "0.0.0.0/0", ] 45 description = "" 46 from_port = 0 47 ipv6_cidr_blocks = [] 48 prefix_list_ids = [] 49 protocol = "-1" 50 security_groups = [] 51 self = false 52 to_port = 0 53 } 54 ] 55 ingress = [ 56 { 57 cidr_blocks = [ "0.0.0.0/0", ] 58 description = "" 59 from_port = 22 60 ipv6_cidr_blocks = [] 61 prefix_list_ids = [] 62 protocol = "tcp" 63 security_groups = [] 64 self = false 65 to_port = 22 66 } 67 ] 68}
-
Finally you need to run the
terraform init, terraform plan and terraform apply
and you should be able to push docker image using the Terraform.
8. Conclusion
This blog post will help you get yourself started with the Terraform && Docker Container and How you can create your own docker image and provision a docker container with the help of Terraform
Also here are key changes that you can do to customize the instructions as per your need -
- Instead of using Node JS you use any other application such as Spring Boot, Python, Angular, ReactJS, GoLang. The only primary requirement is you need to create a Docker container.
- You should always use "aws_ecr_repository" and "aws_ecr_repository_policy" for setting up the ECR container repository then use the
docker build, docker push
command for pushing docker image to ECR.
Posts in this Series
- Securing Sensitive Data in Terraform
- Boost Your AWS Security with Terraform : A Step-by-Step Guide
- How to Load Input Data from a File in Terraform?
- Can Terraform be used to provision on-premises infrastructure?
- Fixing the Terraform Error creating IAM Role. MalformedPolicyDocument Has prohibited field Resource
- In terraform how to handle null value with default value?
- Terraform use module output variables as inputs for another module?
- How to Reference a Resource Created by a Terraform Module?
- Understanding Terraform Escape Sequences
- How to fix private-dns-enabled cannot be set because there is already a conflicting DNS domain?
- Use Terraform to manage AWS IAM Policies, Roles and Users
- How to split Your Terraform main.tf File into Multiple Files
- How to use Terraform variable within variable
- Mastering the Terraform Lookup Function for Dynamic Keys
- Copy files to EC2 and S3 bucket using Terraform
- Troubleshooting Error creating EC2 Subnet InvalidSubnet Range The CIDR is Invalid
- Troubleshooting InvalidParameter Security group and subnet belong to different networks
- Managing strings in Terraform: A comprehensive guide
- How to use terraform depends_on meta argument?
- What is user_data in Terraform?
- Why you should not store terraform state file(.tfstate) inside Git Repository?
- How to import existing resource using terraform import comand?
- Terraform - A detailed guide on setting up ALB(Application Load Balancer) and SSL?
- Testing Infrastructure as Code with Terraform?
- How to remove a resource from Terraform state?
- What is Terraform null Resource?
- In terraform how to skip creation of resource if the resource already exist?
- How to setup Virtual machine on Google Cloud Platform
- How to use Terraform locals?
- Terraform Guide - Docker Containers & AWS ECR(elastic container registry)?
- How to generate SSH key in Terraform using tls_private_key?
- How to fix-Terraform Error acquiring the state lock ConditionalCheckFiledException?
- Terraform Template - A complete guide?
- How to use Terragrunt?
- Terraform and AWS Multi account Setup?
- Terraform and AWS credentials handling?
- How to fix-error configuring S3 Backend no valid credential sources for S3 Backend found?
- Terraform state locking using DynamoDB (aws_dynamodb_table)?
- Managing Terraform states?
- Securing AWS secrets using HashiCorp Vault with Terraform?
- How to use Workspaces in Terraform?
- How to run specific terraform resource, module, target?
- How Terraform modules works?
- Secure AWS EC2s & GCP VMs with Terraform SSH Keys!
- What is terraform provisioner?
- Is terraform destroy needed before terraform apply?
- How to fix terraform error Your query returned no results. Please change your search criteria and try again?
- How to use Terraform Data sources?
- How to use Terraform resource meta arguments?
- How to use Terraform Dynamic blocks?
- Terraform - How to nuke AWS resources and save additional AWS infrastructure cost?
- Understanding terraform count, for_each and for loop?
- How to use Terraform output values?
- How to fix error configuring Terraform AWS Provider error validating provider credentials error calling sts GetCallerIdentity SignatureDoesNotMatch?
- How to fix Invalid function argument on line in provider credentials file google Invalid value for path parameter no file exists
- How to fix error value for undeclared variable a variable named was assigned on the command line?
- What is variable.tf and terraform.tfvars?
- How to use Terraform Variables - Locals,Input,Output
- Terraform create EC2 Instance on AWS
- How to fix Error creating service account googleapi Error 403 Identity and Access Management (IAM) API has not been used in project before or it is disabled
- Install terraform on Ubuntu 20.04, CentOS 8, MacOS, Windows 10, Fedora 33, Red hat 8 and Solaris 11