Introduction
This blog provides a comprehensive guide on how to deploy and host a web application on AWS using a private S3 bucket and CloudFront, leveraging Terraform for infrastructure automation. The private S3 bucket securely stores your application files, while CloudFront acts as a Content Delivery Network (CDN) to serve your application globally with low latency.
Additionally, the guide covers integrating CI/CD pipelines to automate deployments, ensuring that updates to your application are seamless and efficient. This process not only simplifies deployment but also enforces security best practices by using a private S3 bucket, access-controlled CloudFront distribution, and automated workflows to prevent human errors.
By the end of this guide, you’ll have a secure and scalable infrastructure for your web application, complete with error handling, geo-restrictions, and an automated deployment process that aligns with modern DevOps practices.
Folder Structure
Here is the folder structure used in this setup:
provisioning/
├── common/
│ ├── locals.tf
│ ├── main.tf
│ ├── variables.tf
├── dev/
│ ├── main.tf
│ ├── provider.tf
│ ├── state.tf
│ ├── variables.tf
common/
: Contains reusable Terraform modules and shared configuration.dev/
: Environment-specific configurations for development.
Step 1: S3 Bucket and CloudFront Setup
Terraform Code: common/main.tf
resource "aws_s3_bucket" "app" {
bucket = local.s3_bucket_name
tags = merge(local.tags, {
Name = local.s3_bucket_name
})
}
resource "aws_s3_bucket_ownership_controls" "app" {
bucket = aws_s3_bucket.app.id
rule {
object_ownership = "BucketOwnerPreferred"
}
}
resource "aws_s3_bucket_acl" "app" {
depends_on = [aws_s3_bucket_ownership_controls.app]
bucket = aws_s3_bucket.app.id
acl = "private"
}
resource "aws_s3_bucket_policy" "app_bucket_policy" {
bucket = aws_s3_bucket.app.id
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowCloudFrontAccess",
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "s3:GetObject",
"Resource": "${aws_s3_bucket.app.arn}/*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudfront::${var.account_id}:distribution/${aws_cloudfront_distribution.cf_distribution.id}"
}
}
}
]
}
EOF
}
CloudFront Distribution
resource "aws_cloudfront_distribution" "cf_distribution" {
origin {
domain_name = aws_s3_bucket.app.bucket_regional_domain_name
origin_id = local.s3_origin_id
s3_origin_config {
origin_access_identity = aws_cloudfront_origin_access_identity.oai.cloudfront_access_identity_path
}
}
enabled = true
default_root_object = "index.html"
default_cache_behavior {
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
target_origin_id = local.s3_origin_id
viewer_protocol_policy = "redirect-to-https"
}
viewer_certificate {
acm_certificate_arn = var.certificate_arn
ssl_support_method = "sni-only"
}
custom_error_response {
error_code = 403
response_code = 200
response_page_path = "/index.html"
}
}
Step 2: Environment-Specific Configuration
dev/main.tf
module "s3_cloudfront" {
source = "../common"
environment = "dev"
s3_bucket_name = "myapp-dev-bucket"
certificate_arn = "arn:aws:acm:region:account-id:certificate/certificate-id"
}
dev/provider.tf
provider "aws" {
region = "us-east-1"
profile = "default"
}
Step 3: Deploy the Terraform Infrastructure
-
Navigate to the Dev Folder:
cd provisioning/dev
-
Initialize Terraform:
terraform init
-
Plan the Deployment:
terraform plan
-
Apply the Configuration:
terraform apply
Step 4: Setting Up CI/CD
GitHub Actions Workflow for CI/CD
Create a .github/workflows/deploy.yml
file:
name: Deploy to AWS S3 and CloudFront
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v3
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v3
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Deploy Terraform
run: |
cd provisioning/dev
terraform init
terraform apply -auto-approve
Step 5: Test the Deployment
-
Upload Files to S3:
- Use the AWS CLI to upload your web app files to the private S3 bucket:
aws s3 cp ./build s3://myapp-dev-bucket/ --recursive
- Use the AWS CLI to upload your web app files to the private S3 bucket:
-
Access the Application:
- Open the CloudFront distribution domain name in the browser.
Key Features
-
Private S3 Bucket:
- Ensures your files are secure and accessible only via CloudFront.
-
Custom Error Pages:
- Routes 403/404 errors to
index.html
.
- Routes 403/404 errors to
-
Geo-Restrictions:
- Limits access to specific countries.
-
CI/CD Integration:
- Automates deployment with GitHub Actions.
**Conclusion
In this guide, we explored the complete process of deploying a web application using a private S3 bucket and CloudFront, with Terraform as the infrastructure-as-code tool. By combining these powerful AWS services, you can securely store your application files in an S3 bucket while leveraging CloudFront to deliver them efficiently to users across the globe.
We also integrated CI/CD pipelines using GitHub Actions, enabling seamless deployments and reducing manual intervention. This setup not only streamlines your deployment process but also enforces security and scalability, which are critical for modern web applications.
With these steps, you now have a robust, secure, and automated infrastructure ready to host your web applications. Whether you're deploying a small-scale project or a large enterprise app, this approach ensures best practices in security, efficiency, and ease of maintenance.
Start deploying your application confidently, and take advantage of AWS and Terraform to build scalable solutions!