🚀 Week 8: Capstone Project

Day 3: Infrastructure Setup

⏱ Duration: 5 Hours

📚 Learning Objectives

  • Create Terraform configuration for AWS infrastructure
  • Set up VPC with networking components
  • Configure EC2 instance for application deployment
  • Create security groups with proper access rules
  • Build CI/CD pipeline with GitHub Actions

📖 Core Concepts (2 Hours)

Terraform Project Structure

terraform/ ├── main.tf # Provider and main config ├── variables.tf # Input variables ├── outputs.tf # Output values ├── vpc.tf # VPC and networking ├── security_groups.tf # Security groups ├── ec2.tf # EC2 instance └── terraform.tfvars # Variable values (git-ignored)

Main Configuration (main.tf)

# terraform/main.tf terraform { required_version = ">= 1.0.0" required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" } } # Remote state configuration (recommended for team projects) # backend "s3" { # bucket = "your-terraform-state-bucket" # key = "capstone/terraform.tfstate" # region = "us-east-1" # encrypt = true # dynamodb_table = "terraform-locks" # } } provider "aws" { region = var.aws_region default_tags { tags = { Project = "DevOps-Capstone" Environment = var.environment ManagedBy = "Terraform" } } } # Data source for latest Amazon Linux 2 AMI data "aws_ami" "amazon_linux_2" { most_recent = true owners = ["amazon"] filter { name = "name" values = ["amzn2-ami-hvm-*-x86_64-gp2"] } filter { name = "virtualization-type" values = ["hvm"] } }

Variables Configuration (variables.tf)

# terraform/variables.tf variable "aws_region" { description = "AWS region for resources" type = string default = "us-east-1" } variable "environment" { description = "Environment name" type = string default = "production" } variable "project_name" { description = "Project name for resource naming" type = string default = "capstone" } variable "vpc_cidr" { description = "CIDR block for VPC" type = string default = "10.0.0.0/16" } variable "public_subnet_cidr" { description = "CIDR block for public subnet" type = string default = "10.0.1.0/24" } variable "instance_type" { description = "EC2 instance type" type = string default = "t2.micro" } variable "key_name" { description = "SSH key pair name" type = string } variable "docker_image" { description = "Docker image to deploy" type = string default = "your-dockerhub-username/capstone-app:latest" } variable "allowed_ssh_cidr" { description = "CIDR blocks allowed for SSH access" type = list(string) default = ["0.0.0.0/0"] # Restrict in production! }

VPC and Networking (vpc.tf)

# terraform/vpc.tf # VPC resource "aws_vpc" "main" { cidr_block = var.vpc_cidr enable_dns_hostnames = true enable_dns_support = true tags = { Name = "${var.project_name}-vpc" } } # Internet Gateway resource "aws_internet_gateway" "main" { vpc_id = aws_vpc.main.id tags = { Name = "${var.project_name}-igw" } } # Public Subnet resource "aws_subnet" "public" { vpc_id = aws_vpc.main.id cidr_block = var.public_subnet_cidr availability_zone = "${var.aws_region}a" map_public_ip_on_launch = true tags = { Name = "${var.project_name}-public-subnet" } } # Route Table resource "aws_route_table" "public" { vpc_id = aws_vpc.main.id route { cidr_block = "0.0.0.0/0" gateway_id = aws_internet_gateway.main.id } tags = { Name = "${var.project_name}-public-rt" } } # Route Table Association resource "aws_route_table_association" "public" { subnet_id = aws_subnet.public.id route_table_id = aws_route_table.public.id }

Security Groups (security_groups.tf)

# terraform/security_groups.tf # Security Group for EC2 Instance resource "aws_security_group" "app" { name = "${var.project_name}-app-sg" description = "Security group for capstone application" vpc_id = aws_vpc.main.id # SSH access ingress { description = "SSH" from_port = 22 to_port = 22 protocol = "tcp" cidr_blocks = var.allowed_ssh_cidr } # HTTP access ingress { description = "HTTP" from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } # Application port ingress { description = "Application" from_port = 8000 to_port = 8000 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } # All outbound traffic egress { description = "All outbound" from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } tags = { Name = "${var.project_name}-app-sg" } }

EC2 Instance (ec2.tf)

# terraform/ec2.tf # EC2 Instance resource "aws_instance" "app" { ami = data.aws_ami.amazon_linux_2.id instance_type = var.instance_type key_name = var.key_name subnet_id = aws_subnet.public.id vpc_security_group_ids = [aws_security_group.app.id] # User data script to install Docker and run container user_data = <<-EOF #!/bin/bash set -e # Update system yum update -y # Install Docker amazon-linux-extras install docker -y systemctl start docker systemctl enable docker usermod -a -G docker ec2-user # Install Docker Compose curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose chmod +x /usr/local/bin/docker-compose # Pull and run application docker pull ${var.docker_image} docker run -d \ --name capstone-app \ --restart unless-stopped \ -p 80:8000 \ -p 8000:8000 \ -e ENVIRONMENT=production \ -e APP_VERSION=1.0.0 \ ${var.docker_image} # Log completion echo "Setup complete" > /var/log/user-data-complete.log EOF root_block_device { volume_size = 20 volume_type = "gp3" encrypted = true } tags = { Name = "${var.project_name}-app-server" } } # Elastic IP (optional - for static IP) resource "aws_eip" "app" { instance = aws_instance.app.id domain = "vpc" tags = { Name = "${var.project_name}-app-eip" } }

Outputs (outputs.tf)

# terraform/outputs.tf output "vpc_id" { description = "VPC ID" value = aws_vpc.main.id } output "public_subnet_id" { description = "Public subnet ID" value = aws_subnet.public.id } output "instance_id" { description = "EC2 instance ID" value = aws_instance.app.id } output "instance_public_ip" { description = "EC2 instance public IP" value = aws_eip.app.public_ip } output "instance_public_dns" { description = "EC2 instance public DNS" value = aws_instance.app.public_dns } output "app_url" { description = "Application URL" value = "http://${aws_eip.app.public_ip}:8000" } output "health_check_url" { description = "Health check URL" value = "http://${aws_eip.app.public_ip}:8000/health" }

GitHub Actions CI/CD Pipeline

# .github/workflows/cicd.yml name: CI/CD Pipeline on: push: branches: [main] pull_request: branches: [main] env: DOCKER_IMAGE: ${{ secrets.DOCKERHUB_USERNAME }}/capstone-app jobs: test: name: Run Tests runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.11' - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r app/requirements.txt - name: Run tests run: | pytest tests/ -v --tb=short build: name: Build and Push Docker Image needs: test runs-on: ubuntu-latest if: github.event_name == 'push' && github.ref == 'refs/heads/main' steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push uses: docker/build-push-action@v5 with: context: . file: docker/Dockerfile push: true tags: | ${{ env.DOCKER_IMAGE }}:latest ${{ env.DOCKER_IMAGE }}:${{ github.sha }} cache-from: type=gha cache-to: type=gha,mode=max deploy: name: Deploy to EC2 needs: build runs-on: ubuntu-latest if: github.event_name == 'push' && github.ref == 'refs/heads/main' steps: - name: Deploy to EC2 uses: appleboy/ssh-action@v1.0.0 with: host: ${{ secrets.EC2_HOST }} username: ec2-user key: ${{ secrets.EC2_SSH_KEY }} script: | # Pull latest image docker pull ${{ env.DOCKER_IMAGE }}:latest # Stop and remove old container docker stop capstone-app || true docker rm capstone-app || true # Run new container docker run -d \ --name capstone-app \ --restart unless-stopped \ -p 80:8000 \ -p 8000:8000 \ -e ENVIRONMENT=production \ -e APP_VERSION=${{ github.sha }} \ ${{ env.DOCKER_IMAGE }}:latest # Clean up old images docker image prune -f # Verify deployment sleep 10 curl -f http://localhost:8000/health || exit 1

🔬 Hands-on Lab (2.5 Hours)

Lab 1: Create Terraform Configuration

# Navigate to terraform directory cd capstone-project/terraform # Create all terraform files (copy content from above sections) # main.tf, variables.tf, outputs.tf, vpc.tf, security_groups.tf, ec2.tf # Create terraform.tfvars (DO NOT commit this file!) cat > terraform.tfvars << 'EOF' aws_region = "us-east-1" environment = "production" project_name = "capstone" key_name = "your-key-pair-name" docker_image = "your-dockerhub-username/capstone-app:latest" allowed_ssh_cidr = ["YOUR_IP/32"] # Replace with your IP EOF # Add to .gitignore echo "terraform.tfvars" >> ../.gitignore echo "*.tfstate*" >> ../.gitignore echo ".terraform/" >> ../.gitignore

Lab 2: Deploy Infrastructure

# Initialize Terraform terraform init # Validate configuration terraform validate # Format code terraform fmt # Preview changes terraform plan -out=tfplan # Apply changes (creates infrastructure) terraform apply tfplan # Get outputs terraform output # Note the app_url and health_check_url echo "Application URL: $(terraform output -raw app_url)" echo "Health Check: $(terraform output -raw health_check_url)"

Lab 3: Configure GitHub Secrets

# Required GitHub Secrets for CI/CD pipeline: # Settings → Secrets and variables → Actions → New repository secret # 1. DOCKERHUB_USERNAME - Your Docker Hub username # 2. DOCKERHUB_TOKEN - Docker Hub access token (not password!) # 3. EC2_HOST - EC2 public IP from terraform output # 4. EC2_SSH_KEY - Private SSH key for EC2 access # Generate Docker Hub access token: # Docker Hub → Account Settings → Security → New Access Token # Get SSH key: cat ~/.ssh/your-key-pair.pem # Add secrets via GitHub CLI (optional): gh secret set DOCKERHUB_USERNAME gh secret set DOCKERHUB_TOKEN gh secret set EC2_HOST gh secret set EC2_SSH_KEY < ~/.ssh/your-key-pair.pem

Lab 4: Create and Test CI/CD Pipeline

# Create GitHub Actions workflow mkdir -p .github/workflows cat > .github/workflows/cicd.yml << 'EOF' name: CI/CD Pipeline on: push: branches: [main] pull_request: branches: [main] env: DOCKER_IMAGE: ${{ secrets.DOCKERHUB_USERNAME }}/capstone-app jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: '3.11' - run: pip install -r app/requirements.txt - run: pytest tests/ -v build: needs: test runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' steps: - uses: actions/checkout@v4 - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - uses: docker/build-push-action@v5 with: context: . file: docker/Dockerfile push: true tags: ${{ env.DOCKER_IMAGE }}:latest deploy: needs: build runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' steps: - uses: appleboy/ssh-action@v1.0.0 with: host: ${{ secrets.EC2_HOST }} username: ec2-user key: ${{ secrets.EC2_SSH_KEY }} script: | docker pull ${{ env.DOCKER_IMAGE }}:latest docker stop capstone-app || true docker rm capstone-app || true docker run -d --name capstone-app -p 80:8000 ${{ env.DOCKER_IMAGE }}:latest EOF # Commit and push to trigger pipeline git add . git commit -m "Add CI/CD pipeline" git push origin main # Monitor pipeline in GitHub Actions tab echo "Check pipeline: https://github.com/YOUR_USERNAME/capstone-project/actions"

✅ Day 3 Checklist

  • Created Terraform configuration files
  • Configured VPC with public subnet and internet gateway
  • Set up security groups with proper rules
  • Deployed EC2 instance with Docker
  • Created GitHub Actions CI/CD workflow
  • Configured GitHub Secrets for deployment
  • Tested infrastructure deployment