What this guide covers
This guide shows you how to build a small React site, publish the files from dist, and serve them from AWS through a CDN-backed edge endpoint.
Pulumi lets you define and update cloud infrastructure with popular programming languages. In this guide, that code creates the hosting resources, publishes your built site, and exports the DNS values you need when you are ready to delegate a domain.
The first deployment gives you a provider-managed URL you can open right away. Custom domains, DNS delegation, and CI/CD come after that first successful deploy.
What gets deployed
This blueprint follows the same basic flow:
- build the app with
npm run build - upload the generated files from
dist - put a CDN in front of the origin
- export a public URL you can open in the browser
- optionally create a managed DNS zone and the values you need for delegation later
Start with the downloaded example, get one successful deploy working, then swap in your real app and add your real domain workflow when you are ready.
Quickstart
If you want the fastest path to a first public URL, use the downloadable example and follow this sequence:
- Download the example zip at the top of the page and unzip it.
- Open a terminal in the extracted project root.
- Build the website:
cd website
npm install
npm run build
cd ..
- Install the Pulumi dependencies for the language you want to use:
npm install
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
go mod tidy
- For a first local test, you can keep using cloud credentials that already work in your shell. If you want a shared or repeatable setup, use the Pulumi ESC section below before continuing.
- Create the stack and deploy:
pulumi login
pulumi stack init dev
pulumi config set aws:region us-east-1
pulumi up
- When the update finishes, open the site URL:
pulumi stack output siteUrl
You should see the sample site, then you can come back to the later sections for DNS delegation and CI/CD.
On first deploy, the URL is the provider-managed site endpoint that Pulumi creates for you.
Prerequisites
Before you start, make sure you have:
- an AWS account where you can create S3, CloudFront, and Route 53 resources
- a Pulumi account and the Pulumi CLI installed. Pulumi lets you define and update cloud infrastructure with popular programming languages.
- Node.js 20 or newer and npm so you can build the React app
For the Pulumi language you selected:
Set up credentials with Pulumi ESC
Before you run pulumi up, set up Pulumi ESC so you can get short-lived cloud credentials through dynamic login credentials.
If you already have working AWS credentials in your shell and only want a quick local test, you can skip this section and come back later. ESC is the better long-term path for shared environments, Pulumi Deployments, and CI/CD.
Step 1: Create or update an ESC environment
Use imports if you want to layer this on top of a shared base environment.
imports:
- <your-org>/base
values:
aws:
login:
fn::open::aws-login:
oidc:
roleArn: arn:aws:iam::123456789012:role/pulumi-esc
sessionName: pulumi-esc
environmentVariables:
AWS_ACCESS_KEY_ID: ${aws.login.accessKeyId}
AWS_SECRET_ACCESS_KEY: ${aws.login.secretAccessKey}
AWS_SESSION_TOKEN: ${aws.login.sessionToken}
pulumiConfig:
aws:region: us-east-1
This example shows the pieces that matter for AWS:
- the cloud login provider configured for OIDC
- environment variables exported for local CLI use
pulumiConfigvalues passed into your Pulumi stack
Step 2: Attach the environment to your stack
In Pulumi.dev.yaml or your stack config file, add:
environment:
- <your-org>/<your-environment>
That is what makes the ESC environment available to pulumi preview, pulumi up, and pulumi destroy.
Optional: Inspect the environment locally
Step 2 is all Pulumi needs to import the environment during pulumi preview, pulumi up, and pulumi destroy. If you want to sanity-check the resolved values from your shell, run:
esc open <your-org>/<your-environment>
You do not need to run this before pulumi up.
What you get in the download
The downloadable example zip includes:
website/with a minimal React app you can build immediatelyPulumi.yaml- the Pulumi program, dependency files, and reusable website module for the language currently selected below
- a README with a shorter quick start based on this page
index.tsas the Pulumi entrypointcomponents/static-website.tsas the reusable website modulepackage.jsonandtsconfig.jsonfor the root Pulumi project
__main__.pyas the Pulumi entrypointcomponents/static_website.pyas the reusable website modulerequirements.txtfor the root Pulumi project
main.goas the Pulumi entrypointstaticwebsite/site.goas the reusable website modulego.modfor the root Pulumi project
If you want to understand how the app itself is created, the next section walks through the smallest useful scaffold.
Build the app
The example already includes a small React app built with Vite. If you want to create the same kind of blueprint from scratch, use these steps.
Step 1: Create a React app
npm create vite@latest website -- --template react
cd website
npm install
Step 2: Replace src/App.jsx with a tiny app you can recognize after deployment
export default function App() {
return (
<main>
<h1>Hello from React and Pulumi</h1>
<p>Your frontend build is working.</p>
</main>
);
}
Step 3: Build the production files
npm run build
Vite writes the production files to website/dist.
Step 4: Optional local preview
npm run preview
For this AWS React setup, the hosting configuration is set up so deep links load index.html instead of a raw storage 404 page.
Deploy with Pulumi
Follow these steps in order from the project root.
Step 1: Build the website
cd website
npm install
npm run build
cd ..
This creates the files Pulumi will publish from website/dist.
Step 2: Install the root Pulumi dependencies for the language you want to use
The download card and the Pulumi code examples on this page follow the same language selection.
npm install
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
go mod tidy
Step 3: Create a Pulumi stack
If you already created the stack once, run pulumi stack select dev instead. For a fuller walkthrough, see the Pulumi getting started docs.
pulumi login
pulumi stack init dev
pulumi config set aws:region us-east-1
Step 4: Deploy
pulumi up
Approve the preview when Pulumi asks. On the first run, this creates the cloud resources and uploads your built site. Pulumi imports the ESC environment automatically through the environment: reference in your stack config, so you do not need to run esc open <your-org>/<your-environment> first.
Step 5: Open the URL from the Pulumi output
Pulumi prints the public URL at the end of the update. Open that URL in your browser to confirm the site is live.
Get the site URL from stack outputs
After the deployment finishes, you can fetch the public URL again at any time:
pulumi stack output siteUrl
You can also inspect the other edge and DNS-related outputs:
pulumi stack output edgeEndpoint
pulumi stack output dnsZoneNameServers --json
pulumi stack output customDomainHost
pulumi stack output customDomainRecordValue
On AWS, siteUrl points at the CloudFront distribution hostname that Pulumi creates for you.
Plan your DNS handoff
If you want this blueprint to create a managed DNS zone for a domain you control, set these Pulumi config values before pulumi up:
pulumi config set domainName example.com
pulumi config set subdomain www
After deployment:
- run
pulumi stack output dnsZoneNameServers --json - delegate your domain at the registrar or parent DNS provider to those name servers (see the walkthrough below)
- keep using
siteUrlwhile DNS propagates - when you are ready to attach a custom domain at the edge, use
customDomainHost,customDomainRecordType, andcustomDomainRecordValueas the values to carry into that follow-up step
Delegate your domain at your registrar
A domain registrar holds the authoritative NS records for your domain. Delegation tells the public DNS system to ask Amazon Route 53 (the zone this stack created) for records under your domain instead of the registrar’s default nameservers. Until delegation is complete, the Amazon Route 53 zone exists but the internet still resolves your domain at the registrar.
Run the same steps regardless of which registrar you use:
- Copy the four nameserver hostnames from
pulumi stack output dnsZoneNameServers --json. - Sign in to your registrar (for example GoDaddy, Namecheap, Cloudflare Registrar, Squarespace/Google Domains, or Route 53 Registrar) and open the nameserver settings for your domain. Registrars label this area differently, but it is usually under DNS, Nameservers, or Domain settings.
- Switch from the registrar’s default nameservers to custom nameservers and paste the four values Pulumi exported. Save.
- Wait for propagation. Most registrars publish the change in a few minutes. In rare cases allow up to 24 hours.
- Verify with
dig NS example.com +short(ornslookup -type=NS example.comon Windows). The four hostnames in the output should match the ones you pasted.
After delegation on AWS
- Pick the right record for the
subdomainyou configured.pulumi stack output customDomainRecordTypereturnsCNAMEfor this setup, pointing atthe CloudFront distribution hostname. - Subdomain vs apex: a subdomain is any prefixed hostname such as
www.example.comorblog.example.com. The apex is the bare domain with no prefix, such asexample.com. The blueprint defaults to a subdomain, which is the safe default.- Subdomain (recommended): keep the exported record as-is.
- Apex: CNAME records are not allowed at the apex, so swap the exported CNAME for a Route 53 alias record (type
A, alias target: your CloudFront distribution).
- Once delegation is live, follow the blueprint’s custom-domain follow-up to attach a certificate and bind the hostname to Amazon CloudFront. This blueprint intentionally stops before full custom-domain attachment and certificate validation so the first deployment stays small and deterministic.
Set up CI/CD with Pulumi Deployments
Pulumi Deployments is the simplest next step once the local workflow is working.
Use this blueprint flow:
- push the downloaded example to a Git repository
- create the stack in Pulumi Cloud and connect it to that repository
- set the stack’s build command to:
cd website
npm install
npm run build
cd ..
- keep the Pulumi working directory at the repository root so the deployment can see both
website/and the Pulumi project files - configure the same cloud credentials and Pulumi config values in the deployment settings, including
domainNameandsubdomainif you want Pulumi to manage the DNS zone
That gives you the same deployment path from pull requests and merges without having to hand-roll a separate CI script first.
Blueprint Pulumi program
Each download already includes the matching Pulumi entrypoint file and the reusable website module for that language. Use the language tabs to see the exact entrypoint for the version you want to run.
import * as pulumi from "@pulumi/pulumi";
import { existsSync, statSync } from "fs";
import { StaticWebsite } from "./components/static-website";
const buildDir = "website/dist";
if (!existsSync(buildDir) || !statSync(buildDir).isDirectory()) {
throw new Error(`Build output not found at ${buildDir}. Run "cd website && npm run build" first.`);
}
const config = new pulumi.Config();
const website = new StaticWebsite("site", {
buildDir,
domainName: config.get("domainName") ?? undefined,
subdomain: config.get("subdomain") ?? "www",
spaFallbackPath: "/index.html",
tags: {
"solution-family": "static-website",
cloud: "aws",
framework: "react",
language: "typescript",
},
});
export const buildCommand = "npm run build";
export const publishDirectory = buildDir;
export const edgeEndpoint = website.edgeEndpoint;
export const siteUrl = website.siteUrl;
export const dnsZoneName = website.dnsZoneName;
export const dnsZoneNameServers = website.dnsZoneNameServers;
export const customDomainHost = website.customDomainHost;
export const customDomainRecordType = website.customDomainRecordType;
export const customDomainRecordValue = website.customDomainRecordValue;
import os
import pulumi
from components.static_website import StaticWebsite
build_dir = "website/dist"
if not os.path.isdir(build_dir):
raise RuntimeError(
f"Build output not found at {build_dir}. Run \"cd website && npm run build\" first."
)
config = pulumi.Config()
website = StaticWebsite(
"site",
build_dir=build_dir,
domain_name=config.get("domainName"),
subdomain=config.get("subdomain") or "www",
spa_fallback_path="/index.html",
tags={
"solution-family": "static-website",
"cloud": "aws",
"framework": "react",
"language": "python",
},
)
pulumi.export("buildCommand", "npm run build")
pulumi.export("publishDirectory", build_dir)
pulumi.export("edgeEndpoint", website.edge_endpoint)
pulumi.export("siteUrl", website.site_url)
pulumi.export("dnsZoneName", website.dns_zone_name)
pulumi.export("dnsZoneNameServers", website.dns_zone_name_servers)
pulumi.export("customDomainHost", website.custom_domain_host)
pulumi.export("customDomainRecordType", website.custom_domain_record_type)
pulumi.export("customDomainRecordValue", website.custom_domain_record_value)
package main
import (
"fmt"
"os"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi/config"
"static-website-aws-react/staticwebsite"
)
func main() {
pulumi.Run(Program)
}
func Program(ctx *pulumi.Context) error {
buildDir := "website/dist"
if info, err := os.Stat(buildDir); err != nil || !info.IsDir() {
return fmt.Errorf("build output not found at %s. Run \"cd website && npm run build\" first", buildDir)
}
cfg := config.New(ctx, "")
domainName := cfg.Get("domainName")
subdomain := cfg.Get("subdomain")
if subdomain == "" {
subdomain = "www"
}
website, err := staticwebsite.NewStaticWebsite(ctx, "site", &staticwebsite.StaticWebsiteArgs{
BuildDir: buildDir,
DomainName: domainName,
Subdomain: subdomain,
SpaFallbackPath: staticwebsite.StringPtr("/index.html"),
Tags: map[string]string{
"solution-family": "static-website",
"cloud": "aws",
"framework": "react",
"language": "go",
},
})
if err != nil {
return err
}
ctx.Export("buildCommand", pulumi.String("npm run build"))
ctx.Export("publishDirectory", pulumi.String(buildDir))
ctx.Export("edgeEndpoint", website.EdgeEndpoint)
ctx.Export("siteUrl", website.SiteUrl)
ctx.Export("dnsZoneName", website.DnsZoneName)
ctx.Export("dnsZoneNameServers", website.DnsZoneNameServers)
ctx.Export("customDomainHost", website.CustomDomainHost)
ctx.Export("customDomainRecordType", website.CustomDomainRecordType)
ctx.Export("customDomainRecordValue", website.CustomDomainRecordValue)
return nil
}
Reusable components
The entrypoint stays small because the website wiring lives in a reusable module. The downloadable blueprint ships the same component shown below for each language.
import * as aws from "@pulumi/aws";
import * as pulumi from "@pulumi/pulumi";
import * as synced from "@pulumi/synced-folder";
export interface StaticWebsiteArgs {
buildDir: string;
domainName?: string;
subdomain?: string;
spaFallbackPath?: string;
tags?: Record<string, string>;
}
export class StaticWebsite {
public readonly siteUrl: pulumi.Output<string>;
public readonly edgeEndpoint: pulumi.Output<string>;
public readonly dnsZoneName: pulumi.Output<string | undefined>;
public readonly dnsZoneNameServers: pulumi.Output<string[]>;
public readonly customDomainHost: pulumi.Output<string | undefined>;
public readonly customDomainRecordType: pulumi.Output<string | undefined>;
public readonly customDomainRecordValue: pulumi.Output<string | undefined>;
constructor(name: string, args: StaticWebsiteArgs) {
const tags = args.tags ?? {};
const siteBucket = new aws.s3.BucketV2(`${name}-bucket`, {
forceDestroy: true,
tags,
});
new aws.s3.BucketPublicAccessBlock(`${name}-public-access`, {
bucket: siteBucket.id,
blockPublicAcls: true,
blockPublicPolicy: true,
ignorePublicAcls: true,
restrictPublicBuckets: true,
});
const siteOriginAccess = new aws.cloudfront.OriginAccessControl(`${name}-origin`, {
originAccessControlOriginType: "s3",
signingBehavior: "always",
signingProtocol: "sigv4",
});
const siteDistribution = new aws.cloudfront.Distribution(`${name}-cdn`, {
enabled: true,
defaultRootObject: "index.html",
origins: [
{
domainName: siteBucket.bucketRegionalDomainName,
originId: siteBucket.arn,
originAccessControlId: siteOriginAccess.id,
},
],
defaultCacheBehavior: {
targetOriginId: siteBucket.arn,
viewerProtocolPolicy: "redirect-to-https",
allowedMethods: ["GET", "HEAD", "OPTIONS"],
cachedMethods: ["GET", "HEAD", "OPTIONS"],
forwardedValues: {
queryString: false,
cookies: {
forward: "none",
},
},
},
customErrorResponses: args.spaFallbackPath
? [
{
errorCode: 403,
responseCode: 200,
responsePagePath: args.spaFallbackPath,
},
{
errorCode: 404,
responseCode: 200,
responsePagePath: args.spaFallbackPath,
},
]
: undefined,
restrictions: {
geoRestriction: {
restrictionType: "none",
},
},
viewerCertificate: {
cloudfrontDefaultCertificate: true,
},
tags,
});
const bucketPolicy = aws.iam.getPolicyDocumentOutput({
statements: [
{
effect: "Allow",
principals: [
{
type: "Service",
identifiers: ["cloudfront.amazonaws.com"],
},
],
actions: ["s3:GetObject"],
resources: [pulumi.interpolate`${siteBucket.arn}/*`],
conditions: [
{
test: "StringEquals",
variable: "AWS:SourceArn",
values: [siteDistribution.arn],
},
],
},
],
});
new aws.s3.BucketPolicy(`${name}-policy`, {
bucket: siteBucket.bucket,
policy: bucketPolicy.json,
});
new synced.S3BucketFolder(`${name}-files`, {
path: args.buildDir,
bucketName: siteBucket.bucket,
acl: "private",
});
const dnsZone = args.domainName
? new aws.route53.Zone(`${name}-dns`, {
name: args.domainName,
tags,
})
: undefined;
const customDomainHost = args.domainName
? `${args.subdomain ?? "www"}.${args.domainName}`
: undefined;
this.edgeEndpoint = siteDistribution.domainName;
this.siteUrl = pulumi.interpolate`https://${siteDistribution.domainName}`;
this.dnsZoneName = pulumi.output(args.domainName);
this.dnsZoneNameServers = dnsZone ? dnsZone.nameServers : pulumi.output([]);
this.customDomainHost = pulumi.output(customDomainHost);
this.customDomainRecordType = pulumi.output(
customDomainHost ? "CNAME" : undefined,
);
this.customDomainRecordValue = customDomainHost
? siteDistribution.domainName.apply((value) => value)
: pulumi.output(undefined);
}
}
from __future__ import annotations
from dataclasses import dataclass
import pulumi
import pulumi_aws as aws
import pulumi_synced_folder as synced_folder
@dataclass
class StaticWebsite:
edge_endpoint: pulumi.Output[str]
site_url: pulumi.Output[str]
dns_zone_name: pulumi.Output[str | None]
dns_zone_name_servers: pulumi.Output[list[str]]
custom_domain_host: pulumi.Output[str | None]
custom_domain_record_type: pulumi.Output[str | None]
custom_domain_record_value: pulumi.Output[str | None]
def __init__(
self,
name: str,
*,
build_dir: str,
domain_name: str | None,
subdomain: str,
spa_fallback_path: str | None = None,
tags: dict[str, str] | None = None,
) -> None:
tags = tags or {}
site_bucket = aws.s3.BucketV2(f"{name}-bucket", force_destroy=True, tags=tags)
aws.s3.BucketPublicAccessBlock(
f"{name}-public-access",
bucket=site_bucket.id,
block_public_acls=True,
block_public_policy=True,
ignore_public_acls=True,
restrict_public_buckets=True,
)
site_origin_access = aws.cloudfront.OriginAccessControl(
f"{name}-origin",
origin_access_control_origin_type="s3",
signing_behavior="always",
signing_protocol="sigv4",
)
custom_error_responses = None
if spa_fallback_path:
custom_error_responses = [
aws.cloudfront.DistributionCustomErrorResponseArgs(
error_code=403,
response_code=200,
response_page_path=spa_fallback_path,
),
aws.cloudfront.DistributionCustomErrorResponseArgs(
error_code=404,
response_code=200,
response_page_path=spa_fallback_path,
),
]
site_distribution = aws.cloudfront.Distribution(
f"{name}-cdn",
enabled=True,
default_root_object="index.html",
origins=[
aws.cloudfront.DistributionOriginArgs(
domain_name=site_bucket.bucket_regional_domain_name,
origin_id=site_bucket.arn,
origin_access_control_id=site_origin_access.id,
)
],
default_cache_behavior=aws.cloudfront.DistributionDefaultCacheBehaviorArgs(
target_origin_id=site_bucket.arn,
viewer_protocol_policy="redirect-to-https",
allowed_methods=["GET", "HEAD", "OPTIONS"],
cached_methods=["GET", "HEAD", "OPTIONS"],
forwarded_values=aws.cloudfront.DistributionDefaultCacheBehaviorForwardedValuesArgs(
query_string=False,
cookies=aws.cloudfront.DistributionDefaultCacheBehaviorForwardedValuesCookiesArgs(
forward="none",
),
),
),
custom_error_responses=custom_error_responses,
restrictions=aws.cloudfront.DistributionRestrictionsArgs(
geo_restriction=aws.cloudfront.DistributionRestrictionsGeoRestrictionArgs(
restriction_type="none",
),
),
viewer_certificate=aws.cloudfront.DistributionViewerCertificateArgs(
cloudfront_default_certificate=True,
),
tags=tags,
)
bucket_policy = aws.iam.get_policy_document_output(
statements=[
aws.iam.GetPolicyDocumentStatementArgs(
effect="Allow",
principals=[
aws.iam.GetPolicyDocumentStatementPrincipalArgs(
type="Service",
identifiers=["cloudfront.amazonaws.com"],
)
],
actions=["s3:GetObject"],
resources=[site_bucket.arn.apply(lambda arn: f"{arn}/*")],
conditions=[
aws.iam.GetPolicyDocumentStatementConditionArgs(
test="StringEquals",
variable="AWS:SourceArn",
values=[site_distribution.arn],
)
],
)
]
)
aws.s3.BucketPolicy(
f"{name}-policy",
bucket=site_bucket.bucket,
policy=bucket_policy.json,
)
synced_folder.S3BucketFolder(
f"{name}-files",
path=build_dir,
bucket_name=site_bucket.bucket,
acl="private",
)
dns_zone = None
if domain_name:
dns_zone = aws.route53.Zone(f"{name}-dns", name=domain_name, tags=tags)
custom_domain_host = f"{subdomain}.{domain_name}" if domain_name else None
self.edge_endpoint = site_distribution.domain_name
self.site_url = site_distribution.domain_name.apply(lambda host: f"https://{host}")
self.dns_zone_name = pulumi.Output.from_input(domain_name)
self.dns_zone_name_servers = (
dns_zone.name_servers if dns_zone else pulumi.Output.from_input([])
)
self.custom_domain_host = pulumi.Output.from_input(custom_domain_host)
self.custom_domain_record_type = pulumi.Output.from_input(
"CNAME" if custom_domain_host else None
)
self.custom_domain_record_value = (
site_distribution.domain_name if custom_domain_host else pulumi.Output.from_input(None)
)
package staticwebsite
import (
"fmt"
"github.com/pulumi/pulumi-aws/sdk/v6/go/aws/cloudfront"
"github.com/pulumi/pulumi-aws/sdk/v6/go/aws/route53"
"github.com/pulumi/pulumi-aws/sdk/v6/go/aws/s3"
syncedfolder "github.com/pulumi/pulumi-synced-folder/sdk/go/synced-folder"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
type StaticWebsiteArgs struct {
BuildDir string
DomainName string
Subdomain string
SpaFallbackPath *string
Tags map[string]string
}
// StringPtr returns a pointer to the given string. Useful for optional
// struct fields like StaticWebsiteArgs.SpaFallbackPath.
func StringPtr(v string) *string { return &v }
type StaticWebsite struct {
EdgeEndpoint pulumi.StringOutput
SiteUrl pulumi.StringOutput
DnsZoneName pulumi.StringPtrOutput
DnsZoneNameServers pulumi.StringArrayOutput
CustomDomainHost pulumi.StringPtrOutput
CustomDomainRecordType pulumi.StringPtrOutput
CustomDomainRecordValue pulumi.StringPtrOutput
}
func NewStaticWebsite(ctx *pulumi.Context, name string, args *StaticWebsiteArgs) (*StaticWebsite, error) {
siteBucket, err := s3.NewBucketV2(ctx, fmt.Sprintf("%s-bucket", name), &s3.BucketV2Args{
ForceDestroy: pulumi.Bool(true),
Tags: pulumi.ToStringMap(args.Tags),
})
if err != nil {
return nil, err
}
_, err = s3.NewBucketPublicAccessBlock(ctx, fmt.Sprintf("%s-public-access", name), &s3.BucketPublicAccessBlockArgs{
Bucket: siteBucket.ID(),
BlockPublicAcls: pulumi.Bool(true),
BlockPublicPolicy: pulumi.Bool(true),
IgnorePublicAcls: pulumi.Bool(true),
RestrictPublicBuckets: pulumi.Bool(true),
})
if err != nil {
return nil, err
}
originAccess, err := cloudfront.NewOriginAccessControl(ctx, fmt.Sprintf("%s-origin", name), &cloudfront.OriginAccessControlArgs{
OriginAccessControlOriginType: pulumi.String("s3"),
SigningBehavior: pulumi.String("always"),
SigningProtocol: pulumi.String("sigv4"),
})
if err != nil {
return nil, err
}
var customErrorResponses cloudfront.DistributionCustomErrorResponseArray
if args.SpaFallbackPath != nil {
customErrorResponses = cloudfront.DistributionCustomErrorResponseArray{
&cloudfront.DistributionCustomErrorResponseArgs{
ErrorCode: pulumi.Int(403),
ResponseCode: pulumi.Int(200),
ResponsePagePath: pulumi.String(*args.SpaFallbackPath),
},
&cloudfront.DistributionCustomErrorResponseArgs{
ErrorCode: pulumi.Int(404),
ResponseCode: pulumi.Int(200),
ResponsePagePath: pulumi.String(*args.SpaFallbackPath),
},
}
}
distribution, err := cloudfront.NewDistribution(ctx, fmt.Sprintf("%s-cdn", name), &cloudfront.DistributionArgs{
Enabled: pulumi.Bool(true),
DefaultRootObject: pulumi.String("index.html"),
Origins: cloudfront.DistributionOriginArray{
&cloudfront.DistributionOriginArgs{
DomainName: siteBucket.BucketRegionalDomainName,
OriginId: siteBucket.Arn,
OriginAccessControlId: originAccess.ID(),
},
},
DefaultCacheBehavior: &cloudfront.DistributionDefaultCacheBehaviorArgs{
TargetOriginId: siteBucket.Arn,
ViewerProtocolPolicy: pulumi.String("redirect-to-https"),
AllowedMethods: pulumi.StringArray{
pulumi.String("GET"),
pulumi.String("HEAD"),
pulumi.String("OPTIONS"),
},
CachedMethods: pulumi.StringArray{
pulumi.String("GET"),
pulumi.String("HEAD"),
pulumi.String("OPTIONS"),
},
ForwardedValues: &cloudfront.DistributionDefaultCacheBehaviorForwardedValuesArgs{
QueryString: pulumi.Bool(false),
Cookies: &cloudfront.DistributionDefaultCacheBehaviorForwardedValuesCookiesArgs{
Forward: pulumi.String("none"),
},
},
},
CustomErrorResponses: customErrorResponses,
Restrictions: &cloudfront.DistributionRestrictionsArgs{
GeoRestriction: &cloudfront.DistributionRestrictionsGeoRestrictionArgs{
RestrictionType: pulumi.String("none"),
},
},
ViewerCertificate: &cloudfront.DistributionViewerCertificateArgs{
CloudfrontDefaultCertificate: pulumi.Bool(true),
},
Tags: pulumi.ToStringMap(args.Tags),
})
if err != nil {
return nil, err
}
bucketPolicy := pulumi.All(siteBucket.Arn, distribution.Arn).ApplyT(func(values []interface{}) (string, error) {
bucketArn := values[0].(string)
distributionArn := values[1].(string)
return fmt.Sprintf(`{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "s3:GetObject",
"Resource": "%s/*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "%s"
}
}
}
]
}` , bucketArn, distributionArn), nil
}).(pulumi.StringOutput)
_, err = s3.NewBucketPolicy(ctx, fmt.Sprintf("%s-policy", name), &s3.BucketPolicyArgs{
Bucket: siteBucket.Bucket,
Policy: bucketPolicy,
})
if err != nil {
return nil, err
}
_, err = syncedfolder.NewS3BucketFolder(ctx, fmt.Sprintf("%s-files", name), &syncedfolder.S3BucketFolderArgs{
Path: pulumi.String(args.BuildDir),
BucketName: siteBucket.Bucket,
Acl: pulumi.String("private"),
})
if err != nil {
return nil, err
}
var dnsZone *route53.Zone
if args.DomainName != "" {
dnsZone, err = route53.NewZone(ctx, fmt.Sprintf("%s-dns", name), &route53.ZoneArgs{
Name: pulumi.String(args.DomainName),
Tags: pulumi.ToStringMap(args.Tags),
})
if err != nil {
return nil, err
}
}
var nilString *string
result := &StaticWebsite{
EdgeEndpoint: distribution.DomainName,
SiteUrl: distribution.DomainName.ApplyT(func(host string) string {
return fmt.Sprintf("https://%s", host)
}).(pulumi.StringOutput),
DnsZoneName: pulumi.ToOutput(nilString).(pulumi.StringPtrOutput),
DnsZoneNameServers: pulumi.ToOutput([]string{}).(pulumi.StringArrayOutput),
CustomDomainHost: pulumi.ToOutput(nilString).(pulumi.StringPtrOutput),
CustomDomainRecordType: pulumi.ToOutput(nilString).(pulumi.StringPtrOutput),
CustomDomainRecordValue: pulumi.ToOutput(nilString).(pulumi.StringPtrOutput),
}
if args.DomainName != "" {
customDomainHost := fmt.Sprintf("%s.%s", args.Subdomain, args.DomainName)
result.DnsZoneName = pulumi.ToOutput(&args.DomainName).(pulumi.StringPtrOutput)
result.DnsZoneNameServers = dnsZone.NameServers
result.CustomDomainHost = pulumi.ToOutput(&customDomainHost).(pulumi.StringPtrOutput)
recordType := "CNAME"
result.CustomDomainRecordType = pulumi.ToOutput(&recordType).(pulumi.StringPtrOutput)
result.CustomDomainRecordValue = distribution.DomainName.ApplyT(func(value string) *string {
return &value
}).(pulumi.StringPtrOutput)
}
return result, nil
}