Amazon Web Services (AWS) allows you to deploy to their cloud using their Command Line Interface (CLI). While this is not as infrastructure-focused as CloudFormation (or terraform, or CDK, or pulumi), under the hood, it is the same API being called. A common "getting started" example is static web hosting that can be updated from your CI/CD pipeline. To get started, apply your credentials to the CLI instance:
REGION="us-east-1"
aws configure set aws_access_key_id "$ACCESS_KEY_ID"
aws configure set aws_secret_access_key "$SECRET_ACCESS_KEY"
aws configure set region "$REGION"
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
Now, set the parameters for that static site and CI/CD:
DOMAIN="your-web-site.com"
CICDUSER="your-web-site-deploy-user"
POLICY_NAME="YourWebSiteDeployPolicy"
PROFILE="your-web-site-deploy-profile"
BUCKET_NAME="$DOMAIN-static-hosting-$(date +%s)"
DEFAULT_ROOT_OBJECT="index.html"
Since all websites should use HTTPS, generate a certificate (this verifies with email):
CERT_ARN=$(
aws acm request-certificate \
--domain-name "$DOMAIN" \
--subject-alternative-names "*.$DOMAIN" \
--validation-method EMAIL \
--region "$REGION" \
--query CertificateArn \
--output text
)
Now set up the S3 bucket
echo "==> Creating (or verifying existence of) S3 bucket: $BUCKET_NAME in $REGION"
# Check if the bucket already exists
if aws s3api head-bucket --bucket "$BUCKET_NAME" 2>/dev/null; then
echo "Bucket $BUCKET_NAME already exists. Skipping create-bucket."
else
# Bucket does not exist yet → create it
if [ "$REGION" = "us-east-1" ]; then
aws s3api create-bucket \
--bucket "$BUCKET_NAME"
else
aws s3api create-bucket \
--bucket "$BUCKET_NAME" \
--create-bucket-configuration LocationConstraint="$REGION"
fi
echo "Bucket $BUCKET_NAME created."
fi
# UPDATED: Lock down public access at the bucket level (CloudFront will use OAI fetch objects)
echo "==> Applying public access block to $BUCKET_NAME"
aws s3api put-public-access-block \
--bucket "$BUCKET_NAME" \
--public-access-block-configuration BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
echo "Public access block applied."
UPDATED: create a CloudFront Origin Access Identity (OAI):
# We use the current UNIX timestamp as a CallerReference to ensure idempotency
CALLER_REF="oai-$DOMAIN-$(date +%s)"
# Create the OAI
OAI_ID=$(aws cloudfront create-cloud-front-origin-access-identity \
--cloud-front-origin-access-identity-config \
CallerReference="$CALLER_REF",Comment="OAI for $DOMAIN" \
--query "CloudFrontOriginAccessIdentity.Id" \
--output text)
# Some value like E1WVR5QO3X455F
echo "OAI created with ID: $OAI_ID"
# Grab the S3CanonicalUserId for the bucket policy
OAI_S3_CANONICAL_USER=$(aws cloudfront get-cloud-front-origin-access-identity \
--id "$OAI_ID" \
--query "CloudFrontOriginAccessIdentity.S3CanonicalUserId" \
--output text)
# Some value like 2aab88cf5dd3b744b75d2bac01085d01fce0d1600d22436a0afcbf8201d9c5eeb63eb5a822a509164fda91a8e78915ed
echo "Retrieved OAI S3CanonicalUserId: $OAI_S3_CANONICAL_USER"
Attach a bucket policy granting the OAI read access:
echo "==> Attaching bucket policy to allow CloudFront OAI to get objects"
read -r -d '' BUCKET_POLICY_JSON <l<lEOF
{
"Version": "2008-10-17",
"Statement": [
{
"Sid": "GrantCloudFrontOAIReadAccess",
"Effect": "Allow",
"Principal": {
"CanonicalUser": "$OAI_S3_CANONICAL_USER"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::$BUCKET_NAME/*"
}
]
}
EOF
aws s3api put-bucket-policy \
--bucket "$BUCKET_NAME" \
--policy "$BUCKET_POLICY_JSON"
echo "Bucket policy attached."
Create the CloudFront distribution with non-proxy S3 origin (UPDATED using the OAI)
echo "==> Generating CloudFront distribution configuration JSON"
read -r -d '' DIST_CONFIG <<EOF
{
"CallerReference": "$(date +%s)-$DOMAIN",
"PriceClass": "PriceClass_100",
"Aliases": {
"Quantity": 2,
"Items": ["$DOMAIN", "www.$DOMAIN"]
},
"DefaultRootObject": "$DEFAULT_ROOT_OBJECT",
"Origins": {
"Quantity": 1,
"Items": [{
"Id": "S3-$BUCKET_NAME",
"DomainName": "$BUCKET_NAME.s3.amazonaws.com",
"S3OriginConfig": {
"OriginAccessIdentity": "origin-access-identity/cloudfront/$OAI_ID"
}
}]
},
"DefaultCacheBehavior": {
"TargetOriginId": "S3-$BUCKET_NAME",
"ViewerProtocolPolicy": "redirect-to-https",
"AllowedMethods": {
"Quantity": 2,
"Items": ["GET","HEAD"]
},
"Compress": true,
"ForwardedValues": {
"QueryString": false,
"Cookies": {"Forward": "none"}
},
"TrustedSigners": {"Enabled": false,"Quantity": 0},
"TrustedKeyGroups": {"Enabled": false,"Quantity": 0},
"MinTTL": 0
},
"Comment": "CloudFront distribution for $DOMAIN",
"Enabled": true,
"ViewerCertificate": {
"ACMCertificateArn": "$CERT_ARN",
"SSLSupportMethod": "sni-only",
"MinimumProtocolVersion": "TLSv1.2_2018"
},
"Restrictions": {
"GeoRestriction": {"RestrictionType": "none","Quantity": 0}
}
}
EOF
echo "==> Creating CloudFront distribution ..."
DIST_ID=$(aws cloudfront create-distribution \
--distribution-config "$DIST_CONFIG" \
--query "Distribution.Id" \
--output text)
# Something like F11H1ZUR2NWO24
echo "CloudFront distribution created. ID: $DIST_ID"
# Retrieve the domain name of the new distribution
DIST_DOMAIN=$(aws cloudfront get-distribution \
--id "$DIST_ID" \
--query "Distribution.DomainName" \
--output text)
# Something like f2gzio890nkokl.cloudfront.net
echo "CloudFront distribution domain name: $DIST_DOMAIN"
With the static hosting now established, create the CI/CD user
echo "==> Creating CI/CD user"
DIST_ARN=$(
aws cloudfront get-distribution \
--id "$DIST_ID" \
--query "Distribution.ARN" \
--output text
)
echo "Creating IAM user $CICDUSER ..."
aws iam create-user --user-name "$CICDUSER" || echo "User may already exist, continuing ..."
read -r -d '' POLICY <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "CloudFrontCreateInvalidation",
"Effect": "Allow",
"Action": "cloudfront:CreateInvalidation",
"Resource": [
"$DIST_ARN"
]
},
{
"Sid": "AllowListBucket",
"Effect": "Allow",
"Action": ["s3:ListBucket"],
"Resource": "arn:aws:s3:::$BUCKET"
},
{
"Sid": "AllowPutObjects",
"Effect": "Allow",
"Action": ["s3:PutObject","s3:PutObjectAcl"],
"Resource": "arn:aws:s3:::$BUCKET/*"
}
]
}
EOF
echo "Attaching inline policy $POLICY_NAME to $CICDUSER ..."
aws iam put-user-policy \
--user-name "$CICDUSER" \
--policy-name "$POLICY_NAME" \
--policy-document "$POLICY"
read ACCESS_KEY_ID SECRET_ACCESS_KEY < <(
aws iam create-access-key \
--user-name "$CICDUSER" \
--query 'AccessKey.[AccessKeyId,SecretAccessKey]' \
--output text
)
echo "Use the access key $ACCESS_KEY_ID and secret key $SECRET_ACCESS_KEY"
echo "in your development pipeline for $CICDUSER"
Using the user, run the post-build action to update the static contents of the website
echo "==> Running CI/CD post-build action"
echo "Configuring AWS CLI profile [$PROFILE] ..."
aws configure set aws_access_key_id "$ACCESS_KEY_ID" --profile "$PROFILE"
aws configure set aws_secret_access_key "$SECRET_ACCESS_KEY" --profile "$PROFILE"
aws configure set region "$REGION" --profile "$PROFILE"
echo "Syncing the build to the S3 bucket ..."
aws s3 sync build/ s3://$BUCKET_NAME --delete --profile $PROFILE
echo "Making the new contents live ..."
aws cloudfront create-invalidation --distribution-id $DIST_ID --paths "/*" --profile $PROFILE