Using Pulumi IaC to deploy NextJs static website with AWS S3 and EC2

This is a submission for the Pulumi Deploy and Document Challenge: Fast Static Website Deployment Reference: https://www.pulumi.com/templates/static-website/aws/ What I Built Static Website Deployment Next JS Framework publish to s3 and ec2using Pulumi IaC Live Demo Link NA Before After Project Repo NA My Journey I had face issue related to my hardening envt and restriction based sandbox due to security reasons normal production replica:- 1.aws linux 2 flavor with no option to migrate to latest(legacy servers) 2.not using latest nextjs,ts,npm,node version(legacy codebase) 3.unable to create new ec2 4.having s3 public access blocked 5.IAM limited scope and putpolicy for s3 not given Using Pulumi install pulumi verify pulumi verify pulumi specific version configure Pulumi to use a local backend for state before launching pulumi check node and npm version Amazon Linux EC2 /RHEL Based curl -fsSL https://rpm.nodesource.com/setup_16.x | sudo bash - sudo yum install -y nodejs now check node -version (i came across this while troubleshooting) create new directory only in empty directory pulumi project can be created pulumi new aws-typescript now enter values one by one project name: webserver project description: Yashvi Deploying on AWS using Pulumi IaC stack name: Press enter to accept the default dev passphrase to protect config/secrets: Enter Secret123 (you will be prompted to enter this two times) aws:region: us-west-2 Pulumi installed dependencies and Now project ready Now Pulumi to use the IAM role attached to your EC2 instance for AWS authentication pulumi config set aws:skipMetadataApiCheck false You can Refer to the [AWS Classic Pulumi package documentation ] Now to avoid repeated passphrase prompts in Pulumi, set a Bash environment variable: Now to retrieve the public IP address of EC2 instance, execute curl https://checkip.amazonaws.com Now, Open a new browser tab and paste the following URL into the address bar, replacing with the IP address 54.218.250.5 http://54.218.250.5:3000 Trying to add website content and IaC for creating s3 Now by default As we intent Static Website Deployment as per our project submission so i can have nextjs demo app live Replace this part in index.ts import * as pulumi from "@pulumi/pulumi"; import * as aws from "@pulumi/aws"; import * as awsx from "@pulumi/awsx"; // Create an AWS resource (S3 Bucket) const bucket = new aws.s3.BucketV2("my-bucket"); // Export the name of the bucket export const bucketName = bucket.id; with import * as pulumi from "@pulumi/pulumi"; import * as aws from "@pulumi/aws"; import * as fs from "fs"; import * as path from "path"; // 1. Build Next.js app (generates 'out' folder) const nextjsBuildDir = path.join(process.cwd(), "../nextjs-app"); if (!fs.existsSync(nextjsBuildDir)) { throw new Error("Next.js app not found at ../nextjs-app"); } // Simulate build process (run this manually first) // pulumi.log.info("Run 'npm run build' in your Next.js app first!"); // 2. Create S3 bucket with website hosting const siteBucket = new aws.s3.Bucket("nextjs-site", { website: { indexDocument: "index.html", errorDocument: "404.html", }, acl: "public-read", }); // 3. Upload static export files from Next.js const outDir = path.join(nextjsBuildDir, "out"); if (fs.existsSync(outDir)) { const uploadFile = (filePath: string) => { const relativePath = path.relative(outDir, filePath); new aws.s3.BucketObject(relativePath, { bucket: siteBucket.id, source: new pulumi.asset.FileAsset(filePath), contentType: getContentType(filePath), acl: "public-read", }); }; // Recursively upload files const walkDir = (dir: string) => { fs.readdirSync(dir).forEach(file => { const fullPath = path.join(dir, file); if (fs.statSync(fullPath).isDirectory()) { walkDir(fullPath); } else { uploadFile(fullPath); } }); }; walkDir(outDir); } else { // Fallback demo content if Next.js app isn't built new aws.s3.BucketObject("index.html", { bucket: siteBucket.id, content: "Next.js Static Site (Demo)Run 'npm run build' in your Next.js app!", contentType: "text/html", acl: "public-read", }); } // Helper to detect MIME types function getContentType(filePath: string): string { const ext = path.extname(filePath); switch (ext) { case ".html": return "text/html"; case ".css": return "text/css"; case ".js": return "application/javascript"; case ".json": return "application/json"; case ".png": return "image/png"; case ".jpg": case ".jpeg": return "image/jpeg"; default: return "application/octet-stream"; } } // 4. Export URLs export c

Apr 5, 2025 - 20:53
 0
Using Pulumi IaC to deploy NextJs static website with AWS S3 and EC2

This is a submission for the Pulumi Deploy and Document Challenge: Fast Static Website Deployment

Reference: https://www.pulumi.com/templates/static-website/aws/

What I Built

Static Website Deployment Next JS Framework publish to s3 and ec2using Pulumi IaC

Live Demo Link

NA

Before
Image description

After

Image description

Project Repo

NA

My Journey

I had face issue related to my hardening envt and restriction based sandbox due to security reasons normal production replica:-
1.aws linux 2 flavor with no option to migrate to latest(legacy servers)
2.not using latest nextjs,ts,npm,node version(legacy codebase)
3.unable to create new ec2
4.having s3 public access blocked
5.IAM limited scope and putpolicy for s3 not given

Using Pulumi

install pulumi

Image description

verify pulumi

Image description

verify pulumi specific version

Image description

configure Pulumi to use a local backend for state

Image description

before launching pulumi check node and npm version

Image description

Amazon Linux EC2 /RHEL Based

curl -fsSL https://rpm.nodesource.com/setup_16.x | sudo bash -
sudo yum install -y nodejs

Image description

now check node -version (i came across this while troubleshooting)

Image description

create new directory

Image description

only in empty directory pulumi project can be created
pulumi new aws-typescript
now enter values one by one

Image description

project name: webserver
project description: Yashvi Deploying on AWS using Pulumi IaC
stack name: Press enter to accept the default dev
passphrase to protect config/secrets: Enter Secret123 (you will be prompted to enter this two times)
aws:region: us-west-2

Image description

Pulumi installed dependencies and Now project ready

Image description

Now Pulumi to use the IAM role attached to your EC2 instance for AWS authentication

pulumi config set aws:skipMetadataApiCheck false

You can Refer to the [AWS Classic Pulumi package documentation ]

Now to avoid repeated passphrase prompts in Pulumi, set a Bash environment variable:

Image description

Now to retrieve the public IP address of EC2 instance, execute

curl https://checkip.amazonaws.com

Image description

Now, Open a new browser tab and paste the following URL into the address bar, replacing with the IP address 54.218.250.5

http://54.218.250.5:3000

Image description

Trying to add website content and IaC for creating s3

Image description

Now by default

Image description

As we intent Static Website Deployment as per our project submission so i can have nextjs demo app live

Replace this part in index.ts

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
import * as awsx from "@pulumi/awsx";

// Create an AWS resource (S3 Bucket)
const bucket = new aws.s3.BucketV2("my-bucket");

// Export the name of the bucket
export const bucketName = bucket.id;

with

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
import * as fs from "fs";
import * as path from "path";

// 1. Build Next.js app (generates 'out' folder)
const nextjsBuildDir = path.join(process.cwd(), "../nextjs-app");
if (!fs.existsSync(nextjsBuildDir)) {
    throw new Error("Next.js app not found at ../nextjs-app");
}
// Simulate build process (run this manually first)
// pulumi.log.info("Run 'npm run build' in your Next.js app first!");

// 2. Create S3 bucket with website hosting
const siteBucket = new aws.s3.Bucket("nextjs-site", {
    website: {
        indexDocument: "index.html",
        errorDocument: "404.html",
    },
    acl: "public-read",
});

// 3. Upload static export files from Next.js
const outDir = path.join(nextjsBuildDir, "out");
if (fs.existsSync(outDir)) {
    const uploadFile = (filePath: string) => {
        const relativePath = path.relative(outDir, filePath);
        new aws.s3.BucketObject(relativePath, {
            bucket: siteBucket.id,
            source: new pulumi.asset.FileAsset(filePath),
            contentType: getContentType(filePath),
            acl: "public-read",
        });
    };

    // Recursively upload files
    const walkDir = (dir: string) => {
        fs.readdirSync(dir).forEach(file => {
            const fullPath = path.join(dir, file);
            if (fs.statSync(fullPath).isDirectory()) {
                walkDir(fullPath);
            } else {
                uploadFile(fullPath);
            }
        });
    };
    walkDir(outDir);
} else {
    // Fallback demo content if Next.js app isn't built
    new aws.s3.BucketObject("index.html", {
        bucket: siteBucket.id,
        content: "

Next.js Static Site (Demo)

Run 'npm run build' in your Next.js app!", contentType: "text/html", acl: "public-read", }); } // Helper to detect MIME types function getContentType(filePath: string): string { const ext = path.extname(filePath); switch (ext) { case ".html": return "text/html"; case ".css": return "text/css"; case ".js": return "application/javascript"; case ".json": return "application/json"; case ".png": return "image/png"; case ".jpg": case ".jpeg": return "image/jpeg"; default: return "application/octet-stream"; } } // 4. Export URLs export const websiteUrl = siteBucket.websiteEndpoint;

Image description

Structure (making nexjs app folder sibling one)

Image description

Pulumi code in webserver/index.ts should correctly reference the sibling ../nextjs-app folder is must

Image description

I tried for Pulumi IaC for S3 and for EC2 (legacy system and troubleshooting :) )

pulumi up

keep doing yes by y

Now bucket website URL is final output of website deployment