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
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 ApplicationWhy 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 applyBefore 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 planbefore 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.0Before building the image, make sure the application JAR file has been generated:
1
mvn clean packageFor better security and performance:
- Use a small base image.
- Do not run the application as root.
- Add a
.dockerignorefile. - 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: ClusterIPApply the manifests using:
1
2
3
4
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
kubectl get pods
kubectl get servicesAmazon 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:
- Start with a local Kubernetes cluster.
- Deploy a simple application.
- Learn Deployments, Services, ConfigMaps, and Secrets.
- Practice rolling updates and rollbacks.
- Deploy the application to EKS.
- 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:
- Checkout source code.
- Run unit tests.
- Build the application.
- Build the Docker image.
- Scan the image for vulnerabilities.
- Push the image to a container registry.
- Deploy to the Kubernetes cluster.
- 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 testIn 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-appWhen an application fails, check the problem in layers:
- Is the pod running?
- Are there container logs?
- Is the Service selecting the correct pods?
- Is the application listening on the expected port?
- Are security groups and network policies allowing traffic?
- Is the container image available?
- 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, andOwner. - Destroy temporary Terraform environments after practice.
A simple cleanup command for a temporary Terraform environment is:
1
terraform destroyAlways 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.
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article