DEV Community

Atsushi Miyamoto
Atsushi Miyamoto

Posted on • Edited on

Step-by-Step Guide. Hosting Your Application on AWS S3

Are you considering hosting your Next.js static web application on AWS? If so, this article is for you! In this article, I will walk you through the process of setting up AWS S3 and CloudFront for hosting your Next.js application using Terraform. Additionally, I will explain how to establish a robust CI/CD pipeline to seamlessly deploy your application to AWS. By following this step-by-step guide, you'll gain valuable insights and practical knowledge to efficiently host your Next.js application on AWS.

Motivation

Why you should consider hosting CloudFront with S3?
Because it significantly reduces latency, ensuring faster content delivery to end-users. Also, this combination can help reduce costs associated with data transfer and it enhances security measures for your content.
Setting up continuous integration and continuous deployment (CI/CD) to deploy your application becomes easier too.

What is S3?

AWS provides a storage service called S3, which allows you to store data in the form of objects within buckets. Additionally, S3 can be used to host static websites.

What is CloudFront?

CloudFront is a well-known content delivery service, often referred to as a CDN. Its primary function is to efficiently distribute your content to end-users, ensuring fast delivery. One of the main advantages of using CloudFront is that it serves content from edge locations. This means that if your content is already available at an edge location, it can be delivered to users with minimal latency. If the content is not present at an edge location, CloudFront retrieves it from the origin, which in this case would be S3.

Let's start creating CloudFront and S3 by Terraform!

There are 5 steps to deploy your application on Kubernetes with GitHub Actions and ArgoCD.

  1. Setup AWS Provider
  2. Create the S3 bucket
  3. Create the CloudFront
  4. Setup CI/CD

NOTE:
You need to set your aws credential in (~/.aws/credentials)

1. Setup AWS Provider

Configure to execute AWS by Terraform

</> provider.tf

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.0"
    }
  }
  required_version = ">= 1.0"
}

provider "aws" {
  region = "ap-northeast-1"
}
Enter fullscreen mode Exit fullscreen mode

2. Create the S3 bucket

</> s3.tf


resource "aws_s3_bucket" "bucket" {
  bucket = test-bucket
}

resource "aws_s3_bucket_cors_configuration" "cors_config" {
  bucket = aws_s3_bucket.bucket.id

  cors_rule {
    allowed_headers = ["*"]
    allowed_methods = ["GET", "POST", "PUT", "DELETE", "HEAD"]
    allowed_origins = ["https://set-origin"]
    expose_headers  = ["ETag"]
    max_age_seconds = 3000
  }
}

resource "aws_s3_bucket_ownership_controls" "example" {
  bucket = aws_s3_bucket.bucket.id
  rule {
    object_ownership = "ObjectWriter"
  }
}

resource "aws_s3_bucket_acl" "acl" {
  depends_on = [aws_s3_bucket_ownership_controls.example]

  bucket = aws_s3_bucket.bucket.id
  acl    = "private"
}

data "aws_iam_policy_document" "policy" {
  statement {
    actions = [
      "s3:GetObject",
    ]

    resources = [
      "${aws_s3_bucket.bucket.arn}/*",
    ]

    principals {
      type        = "Service"
      identifiers = ["cloudfront.amazonaws.com"]
    }
    condition {
      test     = "StringEquals"
      variable = "aws:SourceArn"
      values   = [aws_cloudfront_distribution.this.arn]
    }
  }
}

resource "aws_s3_bucket_policy" "bucket" {
  bucket = aws_s3_bucket.bucket.id
  policy = data.aws_iam_policy_document.policy.json
}
Enter fullscreen mode Exit fullscreen mode
  • aws_s3_bucket_cors_configuration
    • To enable Cross-Origin Resource Sharing (CORS) and allow loading resources from one domain to interact with resources in a different domain.
  • aws_s3_bucket_acl
    • Set private to disable public access.
  • aws_iam_policy_document
    • To restrict access to only the s3:GetObject action from CloudFront and exclude other policies for content delivery.

3. Create the CloudFront

</> cloudfront.tf

resource "aws_cloudfront_origin_access_control" "origin" {
  name                              = "cf-origin-access-control"
  description                       = "access to s3 bucket"
  origin_access_control_origin_type = "s3"
  signing_behavior                  = "always"
  signing_protocol                  = "sigv4"
}

resource "aws_cloudfront_distribution" "static" {
  default_root_object = "index.html"
  enabled             = true
  price_class         = "PriceClass_200"

  origin {
    domain_name              = aws_s3_bucket.bucket.bucket_regional_domain_name
    origin_id                = "s3-bucket"
    origin_access_control_id = aws_cloudfront_origin_access_control.origin.id
  }

  default_cache_behavior {
    allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
    cached_methods  = ["GET", "HEAD"]

    forwarded_values {
      cookies {
        forward = "none"
      }
      query_string = "false"
    }

    max_ttl                = 86400
    min_ttl                = 0
    target_origin_id       = "s3-bucket"
    viewer_protocol_policy = "redirect-to-https"
  }

  restrictions {
    geo_restriction {
      locations        = []
      restriction_type = "none"
    }
  }

  viewer_certificate {
    cloudfront_default_certificate = true
  }
}
Enter fullscreen mode Exit fullscreen mode
  • aws_cloudfront_origin_access_control
    • Configure the origin type as S3.
  • aws_cloudfront_distribution
    • Create a CloudFront distribution and set the origin to a previously created S3 bucket.
    • To use a custom domain with CloudFront, you can configure your domain by setting it in the viewer_certificate section of your CloudFront distribution settings. In this case, you will be using the CloudFront domain for your custom domain setup.

4. Setup CI/CD

</> deploy.yaml

Creating CI/CD using GitHub Actions allows you to automate the build process and upload files to S3.

on:
  push:
    branches: [release]

permissions:
  id-token: write
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [18]
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
          cache: yarn
      - run: yarn
      - run: yarn build
      - name: Upload artifact
        uses: actions/upload-artifact@v3
        with:
          name: build
          path: dist/
  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - name: Download artifact
        uses: actions/download-artifact@v3
        with:
          name: build
          path: dist/

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1-node16
        with:
          role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/gha-oidc-role
          aws-region: ap-northeast-1

      - name: Publish
        uses: opspresso/action-s3-sync@master
        env:
          AWS_REGION: ap-northeast-1
          FROM_PATH: dist/
          DEST_PATH: s3://bucket

      - name: Invalidate CF
        uses: chetan/invalidate-cloudfront-action@v2
        env:
          AWS_REGION: ap-northeast-1
          DISTRIBUTION: distributionId
          PATHS: /*
Enter fullscreen mode Exit fullscreen mode
  • Upload and Download artifact
    • To share data between jobs, you can utilize the actions/upload-artifact@v3 action.
  • Configure AWS credentials
    • To configure AWS credentials based on your IAM role, you can utilize the aws-actions/configure-aws-credentials@v1-node16 action.
  • Publish
    • To publish your built file, you can utilize the opspresso action-s3-sync@master action.
  • Invalidate CF
    • To remove a file from CloudFront edge caches, you can utilize the chetan/invalidate-cloudfront-action@v2 action.

Conclusion

You have learned how to host and deploy your application using Terraform and GitHub Actions, taking advantage of the managed services provided by AWS such as S3 and CloudFront. With this setup, you can easily manage and scale your application without the need for manual infrastructure management. By following the steps outlined in this article, you can confidently host your application on AWS.

Happy coding!

Reference:

Amazon Simple Storage Service Documentation

Invalidating files - Amazon CloudFront

Amazon CloudFront Documentation

Top comments (0)