Hands-on DevOps Labs¶
A practical lab path for learning how a cloud service is created manually, then reproduced with automation. Every lab follows the same loop:
- Understand the architecture.
- Create the smallest working version.
- Automate the same result.
- Verify, observe, and destroy what you created.
These examples are learning templates. Use a sandbox account, least-privilege credentials, budgets, and short-lived resources. Never commit passwords, access keys, or state files.
Lab 1: AWS EC2, Manual to Terraform¶
Architecture¶
Internet -> Security Group -> EC2 instance -> HTTP service
Manual setup with AWS CLI¶
Set your region first:
export AWS_REGION=us-east-1
aws configure
Find an Ubuntu AMI owned by Canonical:
AMI_ID=$(aws ec2 describe-images \
--region "$AWS_REGION" \
--owners 099720109477 \
--filters 'Name=name,Values=ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*' \
'Name=state,Values=available' \
--query 'sort_by(Images, &CreationDate)[-1].ImageId' \
--output text)
Create a security group and allow HTTP only:
VPC_ID=$(aws ec2 describe-vpcs --region "$AWS_REGION" \
--filters Name=is-default,Values=true \
--query 'Vpcs[0].VpcId' --output text)
SG_ID=$(aws ec2 create-security-group \
--region "$AWS_REGION" \
--group-name devops-lab-web \
--description 'HTTP access for the DevOps lab' \
--vpc-id "$VPC_ID" \
--query GroupId --output text)
aws ec2 authorize-security-group-ingress \
--region "$AWS_REGION" \
--group-id "$SG_ID" \
--protocol tcp --port 80 --cidr 0.0.0.0/0
Launch an instance with user data that installs a web server:
aws ec2 run-instances \
--region "$AWS_REGION" \
--image-id "$AMI_ID" \
--instance-type t3.micro \
--security-group-ids "$SG_ID" \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=devops-lab-web}]' \
--user-data '#!/bin/bash
apt-get update -y
apt-get install -y nginx
systemctl enable --now nginx'
Verify the instance and public address:
aws ec2 describe-instances \
--region "$AWS_REGION" \
--filters Name=tag:Name,Values=devops-lab-web Name=instance-state-name,Values=running \
--query 'Reservations[].Instances[].PublicIpAddress' \
--output text
Reproduce it with Terraform¶
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
variable "aws_region" {
type = string
default = "us-east-1"
}
provider "aws" {
region = var.aws_region
}
data "aws_vpc" "default" {
default = true
}
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"]
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
}
filter {
name = "state"
values = ["available"]
}
}
resource "aws_security_group" "web" {
name = "devops-lab-web"
description = "HTTP access for the DevOps lab"
vpc_id = data.aws_vpc.default.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_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
vpc_security_group_ids = [aws_security_group.web.id]
associate_public_ip_address = true
user_data = <<-USER_DATA
#!/bin/bash
apt-get update -y
apt-get install -y nginx
systemctl enable --now nginx
USER_DATA
tags = {
Name = "devops-lab-web"
ManagedBy = "Terraform"
}
}
output "public_ip" {
value = aws_instance.web.public_ip
}
Run and inspect the plan before applying:
terraform init
terraform fmt -check
terraform validate
terraform plan -out=tfplan
terraform apply tfplan
terraform output public_ip
Clean up when finished:
terraform destroy
Lab 2: Static Website on S3¶
Manual workflow¶
BUCKET="devops-lab-$(date +%s)"
aws s3 mb "s3://$BUCKET" --region "$AWS_REGION"
aws s3 cp ./site/ "s3://$BUCKET/" --recursive
aws s3 sync ./site/ "s3://$BUCKET/" --delete
aws s3 ls "s3://$BUCKET/"
For production, put CloudFront and a private bucket in front of the origin instead of making the bucket public. Add versioning and lifecycle rules for recovery and cost control.
Automation checklist¶
- Bucket name and region are variables.
- Public access remains blocked by default.
- Encryption and versioning are enabled.
- A deployment role can write objects but cannot change account security settings.
- CI runs
terraform fmt -check,terraform validate, andterraform plan.
Lab 3: CI/CD Promotion Flow¶
A useful pipeline separates build, verification, and deployment:
stages:
- stage: Verify
jobs:
- job: Test
steps:
- script: terraform fmt -check
- script: terraform validate
- script: terraform plan -out=tfplan
- stage: Deploy
dependsOn: Verify
condition: succeeded()
jobs:
- deployment: Apply
environment: production
strategy:
runOnce:
deploy:
steps:
- script: terraform apply -auto-approve tfplan
Protect the production environment with approvals, branch policies, secret scanning, and a service connection that has only the permissions required by the deployment.
Verification Checklist¶
- The resource has an owner, environment, and expiry tag.
- Inbound access is limited to required ports and sources.
- Secrets come from a secret manager or pipeline secret, never Git.
- Logs and health metrics are available before production traffic arrives.
- The plan was reviewed before apply.
- Destroy or rollback was tested.
- Cloud budgets and alerts are enabled.
Continue with the detailed AWS guide, Terraform guide, and Azure DevOps guide.