AWS Builder Center

Building a Practical AWS DevOps Environment with Terraform, Docker, and Kubernetes

Learn how to design a practical DevOps environment on AWS using Terraform for infrastructure, Docker for containerization, and Kubernetes for application

Devops Engineer at Jio Platforms Ltd

Building a Practical AWS DevOps Environment with Terraform, Docker, and Kubernetes

Modern application teams need a reliable way to build, deploy, and operate software. AWS provides scalable cloud infrastructure, while DevOps tools such as Terraform, Docker, Kubernetes, and CI/CD platforms help automate the software delivery process.
This article explains how these technologies work together in a practical AWS DevOps environment.

What We Are Building

The goal is to create a deployment workflow with the following components:
  • AWS provides the cloud infrastructure.
  • Terraform provisions and manages infrastructure as code.
  • Docker packages applications into portable containers.
  • Kubernetes manages container deployment and scaling.
  • Git stores application and infrastructure code.
  • CI/CD automates testing and deployment.
A simplified workflow looks like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Developer
|
v
Git Repository
|
v
CI/CD Pipeline
|
+--> Terraform --> AWS Infrastructure
|
+--> Docker Build --> Container Registry
|
v
Kubernetes Cluster
|
v
Running Application

Why Use Infrastructure as Code?

Manually creating cloud resources through the AWS Management Console can be useful for learning, but it becomes difficult to repeat and maintain in real projects.
Terraform allows infrastructure to be described in configuration files. These files can be reviewed, version-controlled, and reused across environments.
For example, a basic AWS provider configuration looks like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
}
}

required_version = ">= 1.5.0"
}

provider "aws" {
region = "ap-south-1"
}
A simple EC2 resource can be defined as follows:
1
2
3
4
5
6
7
8
9
resource "aws_instance" "app_server" {
ami = "ami-xxxxxxxxxxxxxxxxx"
instance_type = "t2.micro"

tags = {
Name = "devops-app-server"
Environment = "development"
}
}
The usual Terraform workflow is:
1
2
3
4
5
terraform init
terraform fmt
terraform validate
terraform plan
terraform apply
Before using an AMI ID, always select an image that is available in the AWS Region configured for your project.

Terraform Best Practices

A maintainable Terraform project should separate reusable configuration from environment-specific values.
A possible structure is:
1
2
3
4
5
6
7
8
9
10
terraform/
├── main.tf
├── variables.tf
├── outputs.tf
├── versions.tf
├── terraform.tfvars
└── modules/
├── networking/
├── compute/
└── security/
Useful practices include:
  • Store Terraform code in Git.
  • Use variables instead of hard-coded values.
  • Keep sensitive values out of the repository.
  • Review terraform plan before applying changes.
  • Use remote state with locking for team projects.
  • Create reusable modules for networking, compute, and security.
  • Use separate environments for development, staging, and production.

Containerizing the Application with Docker

Docker packages an application and its dependencies into a container image. This reduces differences between development, testing, and production environments.
A basic Dockerfile for a Java application may look like this:
1
2
3
4
5
6
7
8
9
FROM eclipse-temurin:17-jre

WORKDIR /app

COPY target/application.jar app.jar

EXPOSE 8080

ENTRYPOINT ["java", "-jar", "app.jar"]
Build and run the image with:
1
2
docker build -t devops-demo-app:1.0 .
docker run -d -p 8080:8080 --name devops-demo devops-demo-app:1.0
Before building the image, make sure the application JAR file has been generated:
1
mvn clean package
For better security and performance:
  • Use a small base image.
  • Do not run the application as root.
  • Add a .dockerignore file.
  • Avoid placing passwords in the Dockerfile.
  • Use image scanning in the CI/CD pipeline.
  • Tag images with meaningful version numbers.

Deploying with Kubernetes

Kubernetes manages containerized applications and provides features such as service discovery, scaling, rolling updates, and self-healing.
A basic Deployment manifest is shown below:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
apiVersion: apps/v1
kind: Deployment
metadata:
name: devops-demo-app
spec:
replicas: 2
selector:
matchLabels:
app: devops-demo-app
template:
metadata:
labels:
app: devops-demo-app
spec:
containers:
- name: application
image: your-registry/devops-demo-app:1.0
ports:
- containerPort: 8080
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "512Mi"
A Kubernetes Service can expose the application internally:
1
2
3
4
5
6
7
8
9
10
11
apiVersion: v1
kind: Service
metadata:
name: devops-demo-service
spec:
selector:
app: devops-demo-app
ports:
- port: 80
targetPort: 8080
type: ClusterIP
Apply the manifests using:
1
2
3
4
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
kubectl get pods
kubectl get services

Amazon EKS or Local Kubernetes?

For learning, tools such as Minikube, kind, or Docker Desktop Kubernetes can provide a local cluster.
For production workloads, Amazon Elastic Kubernetes Service can reduce the operational effort required to manage the Kubernetes control plane. However, EKS still requires careful planning for networking, IAM, node groups, security, monitoring, and cost management.
A useful learning path is:
  1. Start with a local Kubernetes cluster.
  2. Deploy a simple application.
  3. Learn Deployments, Services, ConfigMaps, and Secrets.
  4. Practice rolling updates and rollbacks.
  5. Deploy the application to EKS.
  6. Add monitoring, logging, and autoscaling.

Adding a CI/CD Pipeline

A CI/CD pipeline should automatically validate code and build deployable artifacts.
A typical pipeline includes:
  1. Checkout source code.
  2. Run unit tests.
  3. Build the application.
  4. Build the Docker image.
  5. Scan the image for vulnerabilities.
  6. Push the image to a container registry.
  7. Deploy to the Kubernetes cluster.
  8. Run a smoke test.
Example GitHub Actions workflow:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
name: Build and Deploy

on:
push:
branches:
- main

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Checkout source
uses: actions/checkout@v4

- name: Set up Java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "17"

- name: Build application
run: mvn clean package

- name: Build Docker image
run: docker build -t devops-demo-app:${{ github.sha }} .

- name: Run tests
run: mvn test
In a real deployment pipeline, credentials should be managed securely using the cloud provider's identity and access mechanisms rather than being stored directly in workflow files.

Security Considerations

Security should be included from the beginning of the project instead of being added after deployment.
Important areas include:
  • Follow the principle of least privilege with IAM.
  • Avoid using the root AWS account for daily tasks.
  • Restrict security-group rules to required ports.
  • Encrypt data at rest and in transit.
  • Store secrets in a managed secrets service.
  • Scan container images regularly.
  • Apply operating-system and Kubernetes updates.
  • Enable audit logs and monitoring.
  • Use private subnets for internal services where appropriate.
A common beginner mistake is opening SSH or application ports to the entire internet. Access should be restricted whenever possible.

Monitoring and Troubleshooting

A deployment is not complete until it can be monitored and troubleshot.
Useful commands include:
1
2
3
4
5
6
kubectl get pods
kubectl describe pod <pod-name>
kubectl logs <pod-name>
kubectl get events
kubectl rollout status deployment/devops-demo-app
kubectl rollout history deployment/devops-demo-app
When an application fails, check the problem in layers:
  1. Is the pod running?
  2. Are there container logs?
  3. Is the Service selecting the correct pods?
  4. Is the application listening on the expected port?
  5. Are security groups and network policies allowing traffic?
  6. Is the container image available?
  7. Are environment variables and secrets configured correctly?
This systematic approach is more effective than changing several components at the same time.

Cost Management

Cloud experimentation can create unexpected charges. Before starting a project:
  • Select appropriate instance sizes.
  • Stop or delete unused resources.
  • Remove unused load balancers and public IP addresses.
  • Review storage and snapshot usage.
  • Configure billing alerts.
  • Use tags such as Environment, Project, and Owner.
  • Destroy temporary Terraform environments after practice.
A simple cleanup command for a temporary Terraform environment is:
1
terraform destroy
Always review the plan before confirming resource deletion.

Conclusion

AWS, Terraform, Docker, Kubernetes, and CI/CD form a powerful foundation for learning modern DevOps practices.
The best way to learn is to build the complete workflow incrementally:
  • Provision a small AWS environment.
  • Package an application with Docker.
  • Deploy it to Kubernetes.
  • Automate the workflow with CI/CD.
  • Add security, monitoring, and cost controls.
  • Document what you built and the problems you solved.
A small, repeatable project can provide more practical experience than studying each tool independently.
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article