<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Simone Lusenti</title>
    <description>The latest articles on DEV Community by Simone Lusenti (@lanzone31).</description>
    <link>https://dev.to/lanzone31</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F57252%2F91d12782-27f5-49c9-acc7-b97e06035828.jpg</url>
      <title>DEV Community: Simone Lusenti</title>
      <link>https://dev.to/lanzone31</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lanzone31"/>
    <language>en</language>
    <item>
      <title>Hosting a Next.js (App Router) app on Amazon S3</title>
      <dc:creator>Simone Lusenti</dc:creator>
      <pubDate>Wed, 23 Oct 2024 20:49:53 +0000</pubDate>
      <link>https://dev.to/lanzone31/hosting-a-nextjs-app-router-app-on-amazon-s3-5al6</link>
      <guid>https://dev.to/lanzone31/hosting-a-nextjs-app-router-app-on-amazon-s3-5al6</guid>
      <description>&lt;p&gt;In this tutorial, we'll walk through the steps to deploy a Next.js App Router app on Amazon S3. We'll start by creating and properly configuring an S3 bucket with its CloudFront distribution, then we'll configure Next.js to export a static build. In a future tutorial, we'll explore how to create a deployment pipeline for automatic deployments on every git push, using AWS CodePipeline.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(This website is built with Next.js 15 App Router, exported to a static build, and hosted on Amazon S3.)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;You need an AWS account; there is no charge for the resources we'll create, but you'll be billed for usage when exceeding the free tier limits. To complete the tutorial, you'll need a custom domain (registered on Amazon Route 53 or any other DNS provider) and an ACM certificate associated with it; see the &lt;a href="https://docs.aws.amazon.com/res/latest/ug/acm-certificate.html" rel="noopener noreferrer"&gt;AWS documentation&lt;/a&gt; for more information on how to obtain a certificate.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You will also need the AWS CLI installed and configured with your credentials. See the &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html" rel="noopener noreferrer"&gt;AWS CLI documentation&lt;/a&gt; for more information, and &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sso.html" rel="noopener noreferrer"&gt;AWS CLI SSO configuration&lt;/a&gt; if you need help setting up your account.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;This tutorial is for Next.js 14, Next.js 15 and assumes you're using the App Router. If you don't have a Next.js App Router project, you can create one by running &lt;code&gt;npx create-next-app@latest&lt;/code&gt;. When prompted, always choose the default suggested options.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Since Amazon S3 can only host static contents, we will start by understanding what a static site is and which features are supported.&lt;/p&gt;

&lt;h3&gt;
  
  
  Understanding what's a static site
&lt;/h3&gt;

&lt;p&gt;A static site is a website where the code is generated at build time. This means that the HTML is generated before the user requests it, and the same HTML is served for every request (i.e, it doesn't need to be generated on-demand based on cookies, user sessions, etc.).&lt;/p&gt;

&lt;p&gt;Things you can have in a static site:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Client components, including &lt;code&gt;useState&lt;/code&gt;, animations, and everything you could do with old vanilla React 18 / Next.js 13&lt;/li&gt;
&lt;li&gt;Client components performing client-side network requests (e.g. fetch data from an external API)&lt;/li&gt;
&lt;li&gt;Server components that &lt;strong&gt;can be generated at build time&lt;/strong&gt; (e.g. a blog post page that fetches the post content from a CMS when deploying)&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;When you run next build to generate a static export, Server Components consumed inside the app directory will run during the build, similar to traditional static-site generation. The resulting component will be rendered into static HTML for the initial page load and a static payload for client navigation between routes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Things that are &lt;strong&gt;not&lt;/strong&gt; supported when exporting to a static site:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dynamic routes (there's a way to make dynamic routes work, &lt;a href="https://nextjs.org/docs/app/api-reference/functions/generate-static-params" rel="noopener noreferrer"&gt;see next.js docs for generateStaticParams&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Cookies, headers&lt;/li&gt;
&lt;li&gt;Middleware, Rewrites, Redirects&lt;/li&gt;
&lt;li&gt;Server actions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now that you have decided if you can go with a static site, let's start by creating the infrastructure!&lt;/p&gt;

&lt;h3&gt;
  
  
  Infrastructure overview
&lt;/h3&gt;

&lt;p&gt;We'll create the following resources on AWS; in another tutorial, we'll see how to create this infrastructure with Infrastructure-as-Code using AWS CDK. For now, let's see how to do it manually.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftt4by23eb5yuw72ex2l1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftt4by23eb5yuw72ex2l1.png" alt="Image description" width="800" height="392"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's an outline of what each service will do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Amazon CloudFront&lt;/strong&gt;: The CDN that will serve our static files. Using CloudFront will allow us to use a custom domain name and configure an SSL certificate to serve our site over HTTPS.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amazon S3&lt;/strong&gt;: The storage service that will host our static files. We'll need to enable S3 Static Website Hosting so that our files are served as a website (i.e. &lt;code&gt;index.html&lt;/code&gt; is served when you navigate to the root URL, and subfolders are served as well).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS Certificate Manager (ACM)&lt;/strong&gt;: The service to request and manage our SSL/TLS certificates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amazon Route 53&lt;/strong&gt;: The DNS service that will route our custom domain name to our CloudFront distribution.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS CodePipeline&lt;/strong&gt;: The service to create a pipeline that will automatically deploy our changes to S3 when we push to our Git repository.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Update the Next.js configuration
&lt;/h2&gt;

&lt;p&gt;We need to update the Next.js configuration to export a static build, and to build it in a way that is compatible with S3 Static Website Hosting.&lt;/p&gt;

&lt;p&gt;Locate your &lt;code&gt;next.config.js&lt;/code&gt; (or &lt;code&gt;next.config.mjs&lt;/code&gt;, &lt;code&gt;next.config.ts&lt;/code&gt;) file and add the following properties:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ... existing configuration&lt;/span&gt;

    &lt;span class="nl"&gt;images&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Disable Next.js optimizations for images (it's not available for static sites&lt;/span&gt;
        &lt;span class="c1"&gt;// and will throw an error)&lt;/span&gt;
        &lt;span class="na"&gt;unoptimized&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="c1"&gt;// This tells Next.js to export a static build to the `out` folder&lt;/span&gt;
    &lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;export&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;// This tells Next.js to export pages as "folders with an `index.html` file inside"&lt;/span&gt;
    &lt;span class="c1"&gt;// We use this option so we can avoid having the `.html` extension at the end of the page URLs.&lt;/span&gt;
    &lt;span class="nx"&gt;trailingSlash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Try running &lt;code&gt;next build&lt;/code&gt; and see if it works. Errors are generally self-explanatory, but if you have any issues, please review the checklist above ("Things that are not supported when exporting to a static site").&lt;/p&gt;

&lt;p&gt;If the build succeeds, you should see a new folder called &lt;code&gt;out&lt;/code&gt; with the static build of your site. It will contain the &lt;code&gt;index.html&lt;/code&gt; file for each page, as well as any other assets like images, stylesheets, and all the js files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create the infrastructure
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Create the S3 bucket
&lt;/h3&gt;

&lt;p&gt;Navigate to the &lt;strong&gt;S3 console&lt;/strong&gt;, click on &lt;strong&gt;Create bucket&lt;/strong&gt;, and fill in the following fields:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Region&lt;/strong&gt;: Select the region you want to host your site in (it doesn't really matter since we'll be using CloudFront to serve the files from the closest edge location). You can change the region from the top-right dropdown.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bucket name&lt;/strong&gt;: Your domain name (e.g. &lt;code&gt;allthingsserverless.com&lt;/code&gt;); this doesn't really matter, actually, you can name it whatever you want.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Object ownership&lt;/strong&gt;: Leave it as &lt;strong&gt;ACLs disabled&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Block all public access&lt;/strong&gt;: Uncheck this box - your website will need to be public.&lt;/li&gt;
&lt;li&gt;Acknowledge the warning about the bucket public access.&lt;/li&gt;
&lt;li&gt;Click on &lt;strong&gt;Create bucket&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcriat2xovg2ebq32xc2x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcriat2xovg2ebq32xc2x.png" alt="Image description" width="800" height="704"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Enable static website hosting
&lt;/h3&gt;

&lt;p&gt;The next step is to enable static website hosting for the bucket. This setting is required so that S3 knows how to serve the files as a website (i.e. &lt;code&gt;index.html&lt;/code&gt; is served when you navigate to the root URL, and subfolders are served as well).&lt;/p&gt;

&lt;p&gt;Once the bucket is created, click on it to open the bucket settings page, then:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enter the &lt;strong&gt;Properties&lt;/strong&gt; tab.&lt;/li&gt;
&lt;li&gt;Scroll down to the bottom of the page and find the &lt;strong&gt;Static website hosting&lt;/strong&gt; section.&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;Edit&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Check the &lt;strong&gt;Enable&lt;/strong&gt; box under &lt;strong&gt;Static website hosting&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Confirm &lt;strong&gt;Hosting type&lt;/strong&gt; is set to &lt;strong&gt;Host a static website&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;In &lt;strong&gt;Index document&lt;/strong&gt;, enter &lt;code&gt;index.html&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;In &lt;strong&gt;Error document&lt;/strong&gt;, enter &lt;code&gt;index.html&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Click on &lt;strong&gt;Save changes&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foywproawik5vwnxtjxua.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foywproawik5vwnxtjxua.png" alt="Image description" width="800" height="582"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Allow public read access to the bucket contents
&lt;/h3&gt;

&lt;p&gt;We now need to allow public read access to the bucket contents so that CloudFront can serve the files via the Website Hosting feature.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go back to the bucket settings page, click on the &lt;strong&gt;Permissions&lt;/strong&gt; tab, and find the &lt;strong&gt;Bucket policy&lt;/strong&gt; section.&lt;/li&gt;
&lt;li&gt;Click on &lt;strong&gt;Edit&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Replace the content of the policy with the following:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Principal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::your-domain-name/*"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Make sure to replace &lt;code&gt;your-domain-name&lt;/code&gt; with your actual domain name or bucket name; don't remove the &lt;code&gt;/*&lt;/code&gt; at the end as we need it to allow access to all objects in the bucket&lt;/li&gt;
&lt;li&gt;Click on &lt;strong&gt;Save changes&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Create the CloudFront distribution
&lt;/h3&gt;

&lt;p&gt;Navigate to the &lt;strong&gt;CloudFront console&lt;/strong&gt; (Option+S, CloudFront), click on &lt;strong&gt;Create distribution&lt;/strong&gt;, and fill in the following fields:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;As &lt;strong&gt;Origin domain&lt;/strong&gt;, select the bucket we created in the previous step.&lt;/li&gt;
&lt;li&gt;A warning will tell you you should use the website endpoint; accept the warning by clicking on &lt;strong&gt;Use website endpoint&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcbt8jr572b438s9v87yn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcbt8jr572b438s9v87yn.png" alt="Image description" width="800" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Find the &lt;strong&gt;Viewer protocol policy&lt;/strong&gt; section, and set it to &lt;strong&gt;Redirect HTTP to HTTPS&lt;/strong&gt; to make sure your site is only served over HTTPS&lt;/li&gt;
&lt;li&gt;Scroll down to the &lt;strong&gt;Web Application Firewall (WAF)&lt;/strong&gt; section, and select &lt;strong&gt;Do not enable security protections&lt;/strong&gt;; we don't need them for our simple static site&lt;/li&gt;
&lt;li&gt;Before confirming the distribution creation, continue to the next paragraph.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Attach a custom domain
&lt;/h4&gt;

&lt;p&gt;Find the &lt;strong&gt;Settings&lt;/strong&gt; section towards the bottom of the page:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Under &lt;strong&gt;Alternate domain name (CNAME)&lt;/strong&gt;, press &lt;strong&gt;Add item&lt;/strong&gt; and enter your domain name (e.g. &lt;code&gt;allthingsserverless.com&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Repeat the process for the &lt;strong&gt;Alternate domain name (CNAME)&lt;/strong&gt; section, this time entering &lt;code&gt;www.allthingsserverless.com&lt;/code&gt; (with the www prefix)&lt;/li&gt;
&lt;li&gt;Select the SSL certificate. Only certificates compatible with your domain name will be shown in the dropdown. If you don't have a certificate, see these &lt;a href="https://docs.aws.amazon.com/res/latest/ug/acm-certificate.html" rel="noopener noreferrer"&gt;instructions&lt;/a&gt; on obtaining one.&lt;/li&gt;
&lt;li&gt;Optionally enable HTTP/3&lt;/li&gt;
&lt;li&gt;Click on &lt;strong&gt;Create distribution&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F75bqdeluot9tyf1cl6hr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F75bqdeluot9tyf1cl6hr.png" alt="Image description" width="800" height="558"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Take note of the &lt;strong&gt;Distribution ID&lt;/strong&gt;; we'll need it later, it's a string like &lt;code&gt;E3QEPN1YNJOOW8&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Creating the distribution will take approximately 5 minutes. While waiting for it to be created, you can continue to the next section. Until then, you will see a DNS resolution error.&lt;/p&gt;

&lt;h3&gt;
  
  
  Update the DNS settings
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;If you're using Route 53, follow these instructions:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Navigate to the &lt;strong&gt;Route 53 console&lt;/strong&gt;, click on &lt;strong&gt;Hosted zones&lt;/strong&gt;, and select your domain name.&lt;/li&gt;
&lt;li&gt;Click on the &lt;strong&gt;Create record&lt;/strong&gt; button.&lt;/li&gt;
&lt;li&gt;Set the record type to &lt;strong&gt;A&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Check the &lt;strong&gt;Alias&lt;/strong&gt; option.&lt;/li&gt;
&lt;li&gt;In the &lt;strong&gt;Route traffic to&lt;/strong&gt; field, select &lt;strong&gt;Alias to a CloudFront distribution&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;From the &lt;strong&gt;Choose distribution&lt;/strong&gt; dropdown, select the distribution we need to connect our domain name to. Only distributions with a compatible custom domain name will be shown in the dropdown.&lt;/li&gt;
&lt;li&gt;Click on &lt;strong&gt;Create records&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;If you're not using Route 53:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Please refer to your DNS provider's documentation for more information. You probably need to create a CNAME record having the DNS name of the CloudFront distribution as its target/value. You can find the DNS name of the distribution in the CloudFront console, under the &lt;strong&gt;Distribution Settings&lt;/strong&gt; section.&lt;/p&gt;

&lt;h2&gt;
  
  
  Upload the static files to S3
&lt;/h2&gt;

&lt;p&gt;Now that we have created the infrastructure and configured our Next.js 15 static site, we need to upload it.&lt;/p&gt;

&lt;p&gt;For this tutorial, we'll use a simple script to upload the static files to S3 and invalidate the CloudFront cache. In a later tutorial, we'll see how to create a deployment pipeline for automatic deployments on every git push, using AWS CodePipeline.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You'll need the AWS Command Line Interface. Install the AWS CLI if you don't have it already by following the &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html" rel="noopener noreferrer"&gt;Installing the AWS CLI v2 guide&lt;/a&gt;. You also need to configure the AWS CLI with your credentials; this depends on how your AWS account and organization is set up. I acknowledge IAM can be quite complex to set up, especially if you're not used to it, so I recommend you follow this comprehensive guide by AWS: &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sso.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sso.html&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once you have the AWS CLI installed and configured, create the following script (I name it &lt;code&gt;deploy.sh&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#! /bin/bash -e&lt;/span&gt;

&lt;span class="c"&gt;# Build (next static)&lt;/span&gt;
pnpm run build

&lt;span class="c"&gt;# Copy to S3&lt;/span&gt;
aws s3 &lt;span class="nb"&gt;sync&lt;/span&gt; ./out s3://YOUR-DOMAIN-NAME &lt;span class="nt"&gt;--delete&lt;/span&gt;

&lt;span class="c"&gt;# Invalidate CloudFront cache&lt;/span&gt;
aws cloudfront create-invalidation &lt;span class="nt"&gt;--distribution-id&lt;/span&gt; YOUR_DISTRIBUTION_ID_COPIED_FROM_PREVIOUS_STEP &lt;span class="nt"&gt;--paths&lt;/span&gt; &lt;span class="s2"&gt;"/*"&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's an explanation of what each command does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;pnpm run build&lt;/code&gt;: Runs &lt;code&gt;next build&lt;/code&gt; to generate the static files&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;aws s3 sync ./out s3://YOUR-DOMAIN-NAME --delete&lt;/code&gt;: Copies the static files to the S3 bucket, deleting any files that no longer exist in the &lt;code&gt;out&lt;/code&gt; folder&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;aws cloudfront create-invalidation --distribution-id YOUR_DISTRIBUTION_ID_COPIED_FROM_PREVIOUS_STEP --paths "/*"&lt;/code&gt;: Invalidates the CloudFront cache so that the new files are served (optional)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now make the file executable by running &lt;code&gt;chmod +x deploy.sh&lt;/code&gt;.&lt;br&gt;
You can deploy your changes by running &lt;code&gt;./deploy.sh&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Your changes will be published within a few seconds.&lt;br&gt;
You can run the script again to publish a new version every time you make changes to your site.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;You did it! You are now hosting a Next.js 15 App Router static site on Amazon S3.&lt;br&gt;
It's fully serverless:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it will cost you nothing if no one visits your site&lt;/li&gt;
&lt;li&gt;it will cost you a few cents per month if your site gets visitors&lt;/li&gt;
&lt;li&gt;never goes down&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Remember S3 charges for Storage, Requests and Data Transfer, and CloudFront charges for Requests and Data Transfer. You can use the &lt;a href="https://calculator.aws/#/estimate" rel="noopener noreferrer"&gt;AWS Calculator&lt;/a&gt; to estimate your costs once your free tier usage is exceeded.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>aws</category>
      <category>serverless</category>
    </item>
    <item>
      <title>Part 1 - Deploying a PHP (Laravel) application to Amazon ECS</title>
      <dc:creator>Simone Lusenti</dc:creator>
      <pubDate>Sat, 19 Oct 2024 08:09:53 +0000</pubDate>
      <link>https://dev.to/lanzone31/deploying-php-to-amazon-ecs-fargate-with-codepipeline-the-complete-guide-1f10</link>
      <guid>https://dev.to/lanzone31/deploying-php-to-amazon-ecs-fargate-with-codepipeline-the-complete-guide-1f10</guid>
      <description>&lt;p&gt;In this first part of our tutorial series, we'll walk through the steps to deploy a PHP (Laravel) application to Amazon ECS. We'll start by creating a Docker image, pushing it to Amazon ECR, creating an ECS Task Definition, an ECS Cluster, an ECS Service and connecting a domain name to the service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Working with Docker and ECR
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Create a Dockerfile, and nginx config
&lt;/h3&gt;

&lt;p&gt;In the root of your git repo, create a &lt;code&gt;Dockerfile&lt;/code&gt; with the following contents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Use the official PHP-FPM image as the base&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; public.ecr.aws/docker/library/php:fpm&lt;/span&gt;

&lt;span class="c"&gt;# Define a user variable&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; user=www-data&lt;/span&gt;

&lt;span class="c"&gt;# Install system dependencies and PHP extensions&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    git curl &lt;span class="se"&gt;\
&lt;/span&gt;    libpng-dev &lt;span class="se"&gt;\
&lt;/span&gt;    libonig-dev &lt;span class="se"&gt;\
&lt;/span&gt;    libxml2-dev &lt;span class="se"&gt;\
&lt;/span&gt;    zip unzip libzip-dev &lt;span class="se"&gt;\
&lt;/span&gt;    nginx &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get clean &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker-php-ext-install &lt;span class="se"&gt;\
&lt;/span&gt;        pdo_mysql &lt;span class="se"&gt;\
&lt;/span&gt;        mbstring &lt;span class="se"&gt;\
&lt;/span&gt;        exif &lt;span class="se"&gt;\
&lt;/span&gt;        pcntl &lt;span class="se"&gt;\
&lt;/span&gt;        bcmath &lt;span class="se"&gt;\
&lt;/span&gt;        gd &lt;span class="se"&gt;\
&lt;/span&gt;        zip

&lt;span class="c"&gt;# Install Composer&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=public.ecr.aws/composer/composer:latest-bin /usr/bin/composer /usr/bin/composer&lt;/span&gt;

&lt;span class="c"&gt;# Create a system user for running Composer and Artisan commands&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /home/&lt;span class="nv"&gt;$user&lt;/span&gt;/.composer &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;:&lt;span class="nv"&gt;$user&lt;/span&gt; /home/&lt;span class="nv"&gt;$user&lt;/span&gt;

&lt;span class="c"&gt;# Copy Nginx configuration and entrypoint script&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ./docker/default.conf /etc/nginx/sites-enabled/default&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ./docker/entrypoint.sh /etc/entrypoint.sh&lt;/span&gt;

&lt;span class="c"&gt;# Make the entrypoint script executable&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x /etc/entrypoint.sh

&lt;span class="c"&gt;# Set the working directory&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /var/www&lt;/span&gt;

&lt;span class="c"&gt;# Copy the application code&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --chown=www-data:www-data . /var/www&lt;/span&gt;

&lt;span class="c"&gt;# Install PHP dependencies&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;composer &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="c"&gt;# Expose port 80&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 80&lt;/span&gt;

&lt;span class="c"&gt;# Define the entrypoint&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["/etc/entrypoint.sh"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a new folder named &lt;code&gt;docker&lt;/code&gt; and place the following two files inside.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;docker/entrypoint.sh&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;

&lt;span class="c"&gt;# Start Nginx service&lt;/span&gt;
service nginx start

&lt;span class="c"&gt;# Run Laravel migrations&lt;/span&gt;
php artisan migrate &lt;span class="nt"&gt;--force&lt;/span&gt;

&lt;span class="c"&gt;# Create symbolic link for storage&lt;/span&gt;
php artisan storage:link

&lt;span class="c"&gt;# Clear and optimize the application cache&lt;/span&gt;
php artisan optimize:clear
php artisan optimize

&lt;span class="c"&gt;# Start PHP-FPM&lt;/span&gt;
php-fpm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;docker/default.conf&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt; &lt;span class="s"&gt;default_server&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;index&lt;/span&gt; &lt;span class="s"&gt;index.php&lt;/span&gt; &lt;span class="s"&gt;index.html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;localhost&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;error_log&lt;/span&gt;  &lt;span class="n"&gt;/var/log/nginx/error.log&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;access_log&lt;/span&gt; &lt;span class="n"&gt;/var/log/nginx/access.log&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;root&lt;/span&gt; &lt;span class="n"&gt;/var/www/public&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="p"&gt;~&lt;/span&gt; &lt;span class="sr"&gt;\.php$&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_split_path_info&lt;/span&gt; &lt;span class="s"&gt;^(.+&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;.php)(/.+)&lt;/span&gt;$&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_pass&lt;/span&gt; &lt;span class="nf"&gt;127.0.0.1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;9000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_index&lt;/span&gt; &lt;span class="s"&gt;index.php&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kn"&gt;include&lt;/span&gt; &lt;span class="s"&gt;fastcgi_params&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_param&lt;/span&gt; &lt;span class="s"&gt;SCRIPT_FILENAME&lt;/span&gt; &lt;span class="nv"&gt;$document_root$fastcgi_script_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_param&lt;/span&gt; &lt;span class="s"&gt;PATH_INFO&lt;/span&gt; &lt;span class="nv"&gt;$fastcgi_path_info&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_hide_header&lt;/span&gt; &lt;span class="s"&gt;X-Powered-By&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;# Tells PHP we're using a reverse proxy with TLS termination&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_param&lt;/span&gt; &lt;span class="s"&gt;HTTPS&lt;/span&gt; &lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_param&lt;/span&gt; &lt;span class="s"&gt;HTTP_X_FORWARDED_PROTO&lt;/span&gt; &lt;span class="nv"&gt;$scheme&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_param&lt;/span&gt; &lt;span class="s"&gt;HTTP_X_FORWARDED_SSL&lt;/span&gt; &lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;Content-Security-Policy&lt;/span&gt; &lt;span class="s"&gt;"upgrade-insecure-requests"&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;try_files&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="n"&gt;/index.php?&lt;/span&gt;&lt;span class="nv"&gt;$query_string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And make &lt;code&gt;entrypoint.sh&lt;/code&gt; executable by running &lt;code&gt;chmod +x docker/entrypoint.sh&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pushing the image to Amazon ECR
&lt;/h3&gt;

&lt;p&gt;The first step is to push the image to ECR. You need to perform this step manually first, before you can go ahead and deploy the application to ECS.&lt;/p&gt;

&lt;h4&gt;
  
  
  Create an ECR repository
&lt;/h4&gt;

&lt;p&gt;Create an ECR repository by heading over to the &lt;strong&gt;Amazon ECR console&lt;/strong&gt;, clicking &lt;strong&gt;Create repository&lt;/strong&gt;, and typing in a repository name. For this example, we'll use &lt;code&gt;demo-app&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4o9xvjxn315wqbh1lij6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4o9xvjxn315wqbh1lij6.png" alt="Image description" width="800" height="657"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once created, copy the &lt;strong&gt;URI&lt;/strong&gt; of the repository. You will need this URI later.&lt;/p&gt;

&lt;h4&gt;
  
  
  Push the image to ECR
&lt;/h4&gt;

&lt;p&gt;Select the repository you just created, click on &lt;strong&gt;View push commands&lt;/strong&gt;, and run the commands in your terminal. The commands will look like this (make sure to select the correct region and use the correct Account ID):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws ecr get-login-password &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1 | docker login &lt;span class="nt"&gt;--username&lt;/span&gt; AWS &lt;span class="nt"&gt;--password-stdin&lt;/span&gt; 123456789012.dkr.ecr.us-east-1.amazonaws.com
docker build &lt;span class="nt"&gt;-t&lt;/span&gt; demo-app &lt;span class="nb"&gt;.&lt;/span&gt;
docker tag demo-app:latest 123456789012.dkr.ecr.us-east-1.amazonaws.com/demo-app:latest
docker push 123456789012.dkr.ecr.us-east-1.amazonaws.com/demo-app:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the commands are successfully run, go back to the &lt;strong&gt;Amazon ECR console&lt;/strong&gt;, enter the repository, and confirm that the image has been pushed successfully with the tag &lt;code&gt;latest&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs2ec7chdecpx8z6rwrx8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs2ec7chdecpx8z6rwrx8.png" alt="Image description" width="800" height="249"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Working with Amazon ECS
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Create an ECS Task Definition
&lt;/h4&gt;

&lt;p&gt;The next step is to create an ECS Task Definition with the Docker image we just pushed to ECR.&lt;br&gt;
Start by heading over to the &lt;strong&gt;Amazon ECS console&lt;/strong&gt;, under &lt;strong&gt;Task Definitions&lt;/strong&gt;, click &lt;strong&gt;Create new task definition&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Chose a unique name for the task definition (we'll use &lt;code&gt;demo-app&lt;/code&gt;), and make sure &lt;strong&gt;Fargate&lt;/strong&gt; is selected as the launch type. Don't change anything else in this section for now.&lt;/p&gt;

&lt;p&gt;Scroll down to the &lt;strong&gt;Container - 1&lt;/strong&gt; section, and type in the following values:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name&lt;/strong&gt;: &lt;code&gt;demo-app&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Image&lt;/strong&gt;: &lt;code&gt;123456789012.dkr.ecr.us-east-1.amazonaws.com/demo-app:latest&lt;/code&gt; (replace with your own ECR URI we copied earlier)&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Later, you'll probably want to adjust the memory and CPU settings depending on your application. You can also add Environment Variables and EFS Volumes here, if you need to. We'll cover it in a separate tutorial.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flw0yw7u0vk8vuxe7p8lx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flw0yw7u0vk8vuxe7p8lx.png" alt="Image description" width="800" height="372"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Don't change anything else in this section for now. Scroll down to the bottom and click &lt;strong&gt;Create&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Create an ECS Cluster
&lt;/h4&gt;

&lt;p&gt;We now need to create an ECS Cluster. The cluster is where we'll run the &lt;strong&gt;service&lt;/strong&gt; defined in the task definition we just created.&lt;/p&gt;

&lt;p&gt;Head over to the &lt;strong&gt;Amazon ECS console&lt;/strong&gt;, under &lt;strong&gt;Clusters&lt;/strong&gt;, click &lt;strong&gt;Create cluster&lt;/strong&gt;, type a cluster name, and make sure to select &lt;strong&gt;AWS Fargate (serverless)&lt;/strong&gt; as infrastructure:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faovs5eiuzy3k671prvc5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faovs5eiuzy3k671prvc5.png" alt="Image description" width="800" height="575"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The cluster will take a couple of minutes to create. The cluster creation can occasionally fail, especially on new accounts; just wait a few minutes and try again, choosing a different cluster name.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Create an ECS Service
&lt;/h4&gt;

&lt;p&gt;Open the cluster you just created, scroll down to the &lt;strong&gt;Services&lt;/strong&gt; table, click &lt;strong&gt;Create&lt;/strong&gt;, and type in the following values:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Family&lt;/strong&gt;: &lt;code&gt;demo-app&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Revision&lt;/strong&gt;: &lt;code&gt;1&lt;/code&gt; (leave this as is)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service name&lt;/strong&gt;: &lt;code&gt;demo-app&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo0t5l07h99e1eyb4worb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo0t5l07h99e1eyb4worb.png" alt="Image description" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Do not click &lt;strong&gt;Create&lt;/strong&gt; yet.&lt;/p&gt;

&lt;h4&gt;
  
  
  Add a Load Balancer
&lt;/h4&gt;

&lt;p&gt;Since we're probably serving the application over HTTPS, we'll want to add a load balancer. You cannot do this later.&lt;/p&gt;

&lt;p&gt;Scroll down to the &lt;strong&gt;Networking&lt;/strong&gt; section, and select a VPC you want to deploy the service to. Make sure the VPC has a &lt;strong&gt;Public Subnet&lt;/strong&gt; with an &lt;strong&gt;Internet Gateway&lt;/strong&gt; attached to it. If you don't have a VPC, you can create one by clicking &lt;strong&gt;Create a new VPC&lt;/strong&gt; and following the wizard.&lt;/p&gt;

&lt;p&gt;Once you have selected a VPC, continue reading.&lt;/p&gt;

&lt;p&gt;Scroll down to the &lt;strong&gt;Load balancing&lt;/strong&gt; section, select &lt;strong&gt;Application Load Balancer&lt;/strong&gt;, and select the option &lt;strong&gt;Create a new load balancer&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If this option is not available, you probably didn't select a VPC in the previous step.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Adjust the following values:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Load balancer name&lt;/strong&gt;: &lt;code&gt;demo-app-alb&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Health check grace period&lt;/strong&gt;: &lt;code&gt;300&lt;/code&gt; (I recommend setting this to 300 seconds, which is 5 minutes, to allow your app to start and stabilize)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Under the &lt;strong&gt;Listener&lt;/strong&gt; section, keep the &lt;strong&gt;Create a new listener&lt;/strong&gt; option selected, but adjust the values to use port 443, and the &lt;strong&gt;HTTPS&lt;/strong&gt; protocol. To confirm this selection, you'll need an ACM certificate for the domain you want to use; see the &lt;a href="https://docs.aws.amazon.com/res/latest/ug/acm-certificate.html" rel="noopener noreferrer"&gt;AWS documentation&lt;/a&gt; for more information on how to obtain one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7t7psgh90aduqt0y1qht.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7t7psgh90aduqt0y1qht.png" alt="Image description" width="800" height="298"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Under the &lt;strong&gt;Target group&lt;/strong&gt; section, adjust the following values:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Protocol&lt;/strong&gt;: &lt;code&gt;HTTP&lt;/code&gt; (it's the default, make sure to keep it since our nginx container is listening on port 80)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deregistration delay&lt;/strong&gt;: &lt;code&gt;60&lt;/code&gt; (I recommend to set this to 60 seconds instead of the default 5 minutes to make deployments a bit faster)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Health check path&lt;/strong&gt;: &lt;code&gt;/&lt;/code&gt; (I recommend to set this to a route, such as &lt;code&gt;/healthcheck&lt;/code&gt;, you specifically create in your app; you can leave it as default for now)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5d0gg1mgyn760pf42l10.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5d0gg1mgyn760pf42l10.png" alt="Image description" width="800" height="355"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Create&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Check the Service Status
&lt;/h4&gt;

&lt;p&gt;Services may take a few minutes to show up in the &lt;strong&gt;Services&lt;/strong&gt; table. Just wait a bit and refresh the page if you don't see the new service right away.&lt;/p&gt;

&lt;p&gt;If everything went well, you should see the service listed in the &lt;strong&gt;Services&lt;/strong&gt; table, with a status of &lt;strong&gt;Active&lt;/strong&gt; and &lt;strong&gt;Deployments and tasks&lt;/strong&gt; showing 1/1 running task.&lt;/p&gt;

&lt;p&gt;Deployment errors are shown like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwtjilmm66cmyg373lmop.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwtjilmm66cmyg373lmop.png" alt="Image description" width="800" height="226"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To debug deployment errors, open the service, then click on the &lt;strong&gt;Deployments&lt;/strong&gt; tab; scroll down to the &lt;strong&gt;Events&lt;/strong&gt; section, and click on the most recently started task's Id. The &lt;strong&gt;Logs&lt;/strong&gt; section of the task execution will show you more details about what went wrong.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Laravel usually complains about an incomplete storage folder structure (e.g., missing one of &lt;code&gt;framework&lt;/code&gt;, &lt;code&gt;cache&lt;/code&gt;, &lt;code&gt;sessions&lt;/code&gt;). We'll see how to attach an EFS volume to the task definition to fix this in a separate tutorial.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Connect a domain name to the service
&lt;/h4&gt;

&lt;p&gt;You probably want to connect a domain name to the service we just deployed. In the previous steps, we already created an Application Load Balancer, which is the AWS component responsible for routing internet traffic to the service.&lt;/p&gt;

&lt;p&gt;We also already provisioned an ACM certificate, which is used to encrypt traffic between the end-users and the load balancer.&lt;/p&gt;

&lt;p&gt;To complete the process and make your application accessible over HTTPS from the public internet, you need to create a DNS record that points your domain name to the load balancer. This process is different depending on which DNS provider you're using; please refer to their documentation for more information.&lt;/p&gt;

&lt;p&gt;Start by obtaining the Application Load Balancer's DNS name. Navigate to the Search bar in the AWS Console (Option+S on macOS), type &lt;code&gt;Load Balancer&lt;/code&gt;, and select &lt;strong&gt;Load Balancers (EC2 Feature)&lt;/strong&gt;. You'll see a table with the Load Balancer we need to connect our domain name to:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqk1gw57bre12bkapjzcu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqk1gw57bre12bkapjzcu.png" alt="Image description" width="800" height="216"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Copy the DNS name of the load balancer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you're using Route 53, follow these instructions:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Navigate to the &lt;strong&gt;Route 53 console&lt;/strong&gt;, click on &lt;strong&gt;Hosted zones&lt;/strong&gt;, and select your domain name.&lt;/li&gt;
&lt;li&gt;Click on the &lt;strong&gt;Create record&lt;/strong&gt; button.&lt;/li&gt;
&lt;li&gt;Set the record type to &lt;strong&gt;A&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Check the &lt;strong&gt;Alias&lt;/strong&gt; option.&lt;/li&gt;
&lt;li&gt;In the &lt;strong&gt;Route traffic to&lt;/strong&gt; field, select &lt;strong&gt;Alias to Application and Classic Load Balancer&lt;/strong&gt;, and choose the region of the load balancer.&lt;/li&gt;
&lt;li&gt;From the &lt;strong&gt;Choose load balancer&lt;/strong&gt; dropdown, select the load balancer we need to connect our domain name to. If you have multiple load balancers, check the one you select from the dropdown must match the DNS name of the load balancer we copied earlier.&lt;/li&gt;
&lt;li&gt;Turn off &lt;strong&gt;Evaluate Target Health&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click on &lt;strong&gt;Create records&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;If you're not using Route 53:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Please refer to your DNS provider's documentation for more information. You probably need to create a CNAME record having the DNS name of the load balancer as its target/value.&lt;/p&gt;

&lt;h2&gt;
  
  
  Congratulations
&lt;/h2&gt;

&lt;p&gt;Once you have created the DNS record, wait a few minutes for it to propagate, and then try to access your application through the domain name.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Congratulations!&lt;/strong&gt; You have now successfully deployed a PHP (Laravel) application to AWS ECS.&lt;/p&gt;

&lt;h3&gt;
  
  
  Coming soon in this tutorial series
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Attaching an EFS volume to the task definition to fix Laravel's storage folder structure issue&lt;/li&gt;
&lt;li&gt;Using AWS CodePipeline to automatically deploy new code to the service&lt;/li&gt;
&lt;li&gt;Automating infrastructure provisioning with AWS CDK&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>docker</category>
      <category>serverless</category>
      <category>php</category>
    </item>
  </channel>
</rss>
