Có một team mất 3 ngày để deploy static website lên AWS vì S3 public, ACM ở sai region, CloudFront cache không clear. Câu trả lời cho tất cả những vấn đề đó: một CDK stack + GitHub Actions OIDC, không access key, không chỉnh tay.


  flowchart LR
    Dev["git push main"] --> GHA["GitHub Actions<br/>build + deploy"]
    GHA --> S3["S3 private"]
    S3 --> CF["CloudFront OAC"]
    CF --> R53["Route 53 myapp.com"]
    ACM["ACM us-east-1"] --> CF
    R53 --> User["User"]

CDK Stack

const bucket = new s3.Bucket(this, "WebsiteBucket", {
  blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
  encryption: s3.BucketEncryption.S3_MANAGED,
  enforceSSL: true,
  versioned: true,
});

const zone = route53.HostedZone.fromLookup(this, "Zone", {
  domainName: "myapp.com",
});

const certificate = new acm.Certificate(this, "Cert", {
  domainName: "myapp.com",
  subjectAlternativeNames: ["www.myapp.com"],
  validation: acm.CertificateValidation.fromDns(zone),
});

const distribution = new cloudfront.Distribution(this, "CDN", {
  defaultRootObject: "index.html",
  domainNames: ["myapp.com", "www.myapp.com"],
  certificate,
  defaultBehavior: {
    origin: origins.S3BucketOrigin.withOriginAccessControl(bucket),
    viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
    cachePolicy: cloudfront.CachePolicy.CACHING_OPTIMIZED,
  },
  errorResponses: [
    {
      httpStatus: 404,
      responseHttpStatus: 200,
      responsePagePath: "/index.html",
    },
  ],
});

new route53.ARecord(this, "AliasRecord", {
  zone,
  target: route53.RecordTarget.fromAlias(
    new route53targets.CloudFrontTarget(distribution)
  ),
});

new s3deploy.BucketDeployment(this, "Deploy", {
  sources: [s3deploy.Source.asset("../dist")],
  destinationBucket: bucket,
  distribution,
  distributionPaths: ["/*"],
});

GitHub Actions OIDC

Mình thấy nhiều team vẫn dùng access key trong CI dù AWS đã có OIDC từ năm 2022. Lý do: setup ban đầu hơi rắc rối, nhưng làm một lần xong quên.

name: Deploy
on: push
permissions:
  id-token: write
  contents: read
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/github-actions-deploy
          aws-region: ap-southeast-1
      - run: npm ci && npm run build
      - run: aws s3 sync dist/ s3://myapp-website --delete
      - run: aws cloudfront create-invalidation --distribution-id $CF_ID --paths "/*"

OIDC deploy không access key là thứ mất 1 tiếng setup mà mỗi lần deploy sau đó đều tiết kiệm được vài phút loay hoay với credential. Và nó cũng là prerequisite để CI/CD pipeline của bạn pass security review.

  • S3 private + CloudFront OAC: không public bucket
  • ACM us-east-1: bắt buộc cho CloudFront
  • SPA fallback: 404 → 200 /index.html
  • OIDC deploy: không access key, token expire 1h

Bài sau: Phần 20: ECR — container image registry