<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ S3 - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ S3 - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Wed, 24 Jun 2026 22:47:01 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/s3/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Self‑Host an S3‑Compatible Object Store with MinIO on Your Staging Server (and Save Hundreds of Dollars a Month) ]]>
                </title>
                <description>
                    <![CDATA[ This article is a complete copy‑paste guide to running MinIO behind Traefik with HTTPS, custom domains, and pre-signed upload/download URLs — using only Docker Compose. Your production will keep using ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-self-host-an-s3-compatible-object-store-with-minio-on-your-staging-server/</link>
                <guid isPermaLink="false">6a1d99eb2f5663bb4c520a8f</guid>
                
                    <category>
                        <![CDATA[ Devops ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                    <category>
                        <![CDATA[ cloud-storage ]]>
                    </category>
                
                    <category>
                        <![CDATA[ S3 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Md Tarikul Islam ]]>
                </dc:creator>
                <pubDate>Mon, 01 Jun 2026 14:40:43 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/a7e1dd1d-2e31-4d80-ae9b-10242588a5e1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>This article is a complete copy‑paste guide to running MinIO behind Traefik with HTTPS, custom domains, and pre-signed upload/download URLs — using only Docker Compose.</p>
<p>Your production will keep using a managed S3 / Cloudflare R2 / Hetzner Object Storage, while every staging upload, download, and pre-signed URL goes to your <strong>own</strong> server for free.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-1-why-selfhost-object-storage-on-staging">1. Why Self‑Host Object Storage on Staging?</a></p>
</li>
<li><p><a href="#heading-2-the-architecture-production-vs-staging">2. The Architecture: Production vs. Staging</a></p>
</li>
<li><p><a href="#heading-3-prerequisites">3. Prerequisites</a></p>
</li>
<li><p><a href="#heading-4-step-1-dns-point-your-domains-to-the-staging-server">4. Step 1 — DNS: Point Your Domains to the Staging Server</a></p>
</li>
<li><p><a href="#heading-5-step-2-run-minio-with-docker-compose">5. Step 2 — Run MinIO with Docker Compose</a></p>
</li>
<li><p><a href="#heading-6-step-3-expose-minio-over-https-with-traefik">6. Step 3 — Expose MinIO over HTTPS with Traefik</a></p>
</li>
<li><p><a href="#heading-7-step-4-create-the-bucket-and-access-keys">7. Step 4 — Create the Bucket and Access Keys</a></p>
</li>
<li><p><a href="#heading-8-step-5-configure-your-app-to-use-minio-on-staging-only">8. Step 5 — Configure Your App to Use MinIO on Staging Only</a></p>
</li>
<li><p><a href="#heading-9-step-6-upload-files-3-ways">9. Step 6 — Upload Files (3 Ways)</a></p>
</li>
<li><p><a href="#heading-10-step-7-generate-presigned-urls-put-and-get">10. Step 7 — Generate Presigned URLs (PUT and GET)</a></p>
</li>
<li><p><a href="#heading-11-step-8-get-public-urls-for-documents">11. Step 8 — Get Public URLs for Documents</a></p>
</li>
<li><p><a href="#heading-12-step-9-lock-down-cors-lifecycle-and-security">12. Step 9 — Lock Down CORS, Lifecycle, and Security</a></p>
</li>
<li><p><a href="#heading-13-step-10-backups-and-monitoring">13. Step 10 — Backups and Monitoring</a></p>
</li>
<li><p><a href="#heading-14-troubleshooting-cheat-sheet">14. Troubleshooting Cheat Sheet</a></p>
</li>
<li><p><a href="#heading-15-wrapping-up">15. Wrapping Up</a></p>
</li>
</ul>
<h2 id="heading-1-why-selfhost-object-storage-on-staging">1. Why Self‑Host Object Storage on Staging?</h2>
<p>If your app handles documents — PDFs, profile pictures, application transcripts, recordings — every test upload your QA team makes costs real money on AWS S3, Cloudflare R2, or Hetzner Object Storage. The price isn't huge per file, but staging is where you:</p>
<ul>
<li><p>run automated end‑to‑end tests that upload thousands of dummy files,</p>
</li>
<li><p>reset databases nightly (which leaves orphan objects behind),</p>
</li>
<li><p>let developers experiment with broken code that re‑uploads the same files,</p>
</li>
<li><p>and hold months of test data nobody ever deletes.</p>
</li>
</ul>
<p>In production those costs are justified. Managed storage gives you replication, availability, and someone else's pager. In staging, those costs are pure waste.</p>
<p><a href="https://min.io/"><strong>MinIO</strong></a> is a free, open‑source, S3‑compatible object server. Same API, same SDKs, same presigned URLs, same <code>mc</code>/<code>aws s3</code> CLIs — but running on your own VPS, billed at $0 per gigabyte. Point your staging app at MinIO, point your production app at S3/R2, and the only thing that changes is an environment variable.</p>
<p><strong>The result:</strong> identical code paths in both environments, zero storage bill on staging, and a nice fallback if your cloud provider ever has an outage.</p>
<h2 id="heading-2-the-architecture-production-vs-staging">2. The Architecture: Production vs. Staging</h2>
<p>In real-world applications, you usually don’t want your development or staging environment writing directly to production storage.</p>
<p>A common and cost-effective setup is:</p>
<ul>
<li><p><strong>Production</strong>: managed cloud object storage</p>
</li>
<li><p><strong>Staging / Development</strong>: self-hosted S3-compatible storage</p>
</li>
</ul>
<p>The good part is that your application code doesn't need to change.</p>
<p>As long as both services are S3-compatible, the same SDK and upload logic work everywhere. Only the environment variables differ.</p>
<h3 id="heading-high-level-architecture">High-Level Architecture</h3>
<img src="https://cdn.hashnode.com/uploads/covers/66cb39fcaa2a09f9a8d691c1/01ddeefd-8a67-42e3-a3af-9b1d3664bdb2.png" alt="High-level architecture showing a Next.js application uploading files to Cloudflare R2 in production and MinIO in staging through the same S3-compatible API." style="display:block;margin:0 auto" width="426" height="421" loading="lazy">

<p>The above diagram illustrates how the same application can communicate with different storage providers depending on the deployment environment.</p>
<p>In the <strong>production environment</strong>, uploads are stored in a managed object storage service such as AWS S3, Cloudflare R2, or Hetzner Object Storage. These services handle durability, scalability, backups, and infrastructure management.</p>
<p>In the <strong>staging environment</strong>, uploads are directed to a self-hosted MinIO instance running inside Docker on a VPS. MinIO implements the S3 API, making it behave similarly to production storage while keeping costs low.</p>
<p>Because both storage systems are S3-compatible, the application uses the same upload logic in every environment. The only difference is the configuration provided through environment variables.</p>
<h3 id="heading-why-this-architecture-is-useful">Why This Architecture Is Useful</h3>
<p>This setup gives you:</p>
<ul>
<li><p>A cheap staging environment</p>
</li>
<li><p>Production-like testing</p>
</li>
<li><p>Zero storage vendor lock-in</p>
</li>
<li><p>The ability to switch providers without rewriting application code</p>
</li>
</ul>
<p>Because both environments speak the S3 protocol, your upload logic remains identical.</p>
<h3 id="heading-example-environment-variables">Example Environment Variables</h3>
<p>Your application only reads environment variables like these:</p>
<pre><code class="language-xml">S3_ENDPOINT=
S3_REGION=
S3_ACCESS_KEY=
S3_SECRET_KEY=
S3_BUCKET=
</code></pre>
<p>Switch the values, and the exact same application now uploads files to a different backend.</p>
<h3 id="heading-production-storage-example">Production Storage Example</h3>
<p>In production, you typically use managed object storage providers such as:</p>
<ul>
<li><p>AWS S3</p>
</li>
<li><p>Cloudflare R2</p>
</li>
<li><p>Hetzner Object Storage</p>
</li>
</ul>
<p>Example:</p>
<pre><code class="language-plaintext">S3_ENDPOINT=https://&lt;region&gt;.r2.cloudflarestorage.com
</code></pre>
<p>The benefits are that it's highly scalable, globally available, durable, has managed backups, and doesn't have infrastructure maintenance.</p>
<h3 id="heading-staging-environment-example">Staging Environment Example</h3>
<p>For staging, a lightweight self-hosted MinIO container is often enough.</p>
<pre><code class="language-plaintext">Next.js App
     ↓
MinIO Container (inside Docker on VPS)
</code></pre>
<p>Example domains:</p>
<table>
<thead>
<tr>
<th>Service</th>
<th>Domain</th>
<th>Internal Port</th>
</tr>
</thead>
<tbody><tr>
<td>MinIO S3 API</td>
<td><a href="http://minio-staging.domain.com"><code>minio-staging.domain.com</code></a></td>
<td><code>9000</code></td>
</tr>
<tr>
<td>MinIO Web Console</td>
<td><a href="http://minio-console-staging.domain.com"><code>minio-console-staging.domain.com</code></a></td>
<td><code>9001</code></td>
</tr>
</tbody></table>
<p>This allows you to:</p>
<ul>
<li><p>Test uploads safely</p>
</li>
<li><p>Avoid production storage costs</p>
</li>
<li><p>Reproduce production-like behavior locally</p>
</li>
</ul>
<h2 id="heading-3-prerequisites">3. Prerequisites</h2>
<p>You'll need:</p>
<ul>
<li><p>A Linux VPS (Hetzner, DigitalOcean, Contabo, OVH — anything with a public IP).</p>
</li>
<li><p>Two A records pointing at that IP (we'll register them next).</p>
</li>
<li><p>Docker + Docker Compose v2.</p>
</li>
<li><p><a href="https://traefik.io/">Traefik</a> v2 in front, with Let's Encrypt configured (any reverse proxy works&nbsp;– the labels below are Traefik's flavor).</p>
</li>
<li><p>Open ports <code>80</code> and <code>443</code> on the firewall for Let's Encrypt + HTTPS.</p>
</li>
<li><p>~10 GB free disk for the MinIO data volume to start.</p>
</li>
</ul>
<p>If Docker isn't installed:</p>
<pre><code class="language-bash">curl -fsSL https://get.docker.com | sh
sudo apt-get install -y docker-compose-plugin
docker --version &amp;&amp; docker compose version
</code></pre>
<h2 id="heading-4-step-1-dns-point-your-domains-to-the-staging-server">4. Step 1 — DNS: Point Your Domains to the Staging Server</h2>
<p>In your DNS provider (Cloudflare, Route 53, Namecheap, and so on), create two <strong>A records</strong> pointing at your staging server's public IP:</p>
<pre><code class="language-plaintext">minio-staging.domain.com           A    203.0.113.45
minio-console-staging.domain.com   A    203.0.113.45
</code></pre>
<p>If you use Cloudflare, set the proxy status to <strong>DNS only</strong> (gray cloud) for <code>minio-staging.*</code>. Cloudflare's free plan caps uploads at 100 MB, and you don't want it stripping S3 signing headers. The console subdomain can stay proxied if you want a WAF in front of it.</p>
<p>Wait a minute and verify:</p>
<pre><code class="language-bash">dig +short minio-staging.domain.com
# 203.0.113.45
</code></pre>
<h2 id="heading-5-step-2-run-minio-with-docker-compose">5. Step 2 — Run MinIO with Docker Compose</h2>
<p>Add this service to your staging compose file (<code>docker-compose.staging.yml</code>). MinIO is just one container — the disk is mounted as a Docker volume so data survives upgrades.</p>
<pre><code class="language-yaml"># docker-compose.staging.yml
networks:
  proxy:
    external: true
    name: proxy
  internal:
    name: internal

volumes:
  minio-data:

services:
  minio:
    image: minio/minio:latest
    container_name: minio-staging
    restart: unless-stopped
    environment:
      - MINIO_ROOT_USER=${MINIO_ROOT_USER:-admin}
      - MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD:-change-me-please}
      # Tell MinIO which public domain to sign URLs with
      - MINIO_SERVER_URL=https://minio-staging.domain.com
      - MINIO_BROWSER_REDIRECT_URL=https://minio-console-staging.domain.com
    command: server /data --console-address ":9001"
    volumes:
      - minio-data:/data
    networks:
      - proxy
      - internal
    ports:
      - "9000:9000"  # S3 API
      - "9001:9001"  # Web console
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 30s
</code></pre>
<p>Two things deserve attention:</p>
<ul>
<li><p><code>MINIO_SERVER_URL</code> is the secret sauce. Without it, MinIO signs presigned URLs using its internal hostname (<code>http://minio:9000</code>), which then fails verification when the browser hits the public domain. Set it to the exact HTTPS URL clients will use.</p>
</li>
<li><p><code>MINIO_BROWSER_REDIRECT_URL</code> does the same for the web console (login redirects, OIDC callbacks, and so on).</p>
</li>
</ul>
<p>Bring it up:</p>
<pre><code class="language-bash">docker compose -f docker-compose.staging.yml up -d minio
docker compose -f docker-compose.staging.yml logs -f minio
</code></pre>
<p>You should see <code>API: http://...</code> and <code>Console: http://...</code> lines.</p>
<h2 id="heading-6-step-3-expose-minio-over-https-with-traefik">6. Step 3 — Expose MinIO over HTTPS with Traefik</h2>
<p>We don't expose ports <code>9000</code>/<code>9001</code> to the world directly — Traefik does that for us, terminating TLS with a free Let's Encrypt certificate.</p>
<p>Add these labels to the <code>minio</code> service:</p>
<pre><code class="language-yaml">    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=proxy"

      # ---- S3 API (port 9000) ----
      - "traefik.http.routers.minio-staging.rule=Host(`minio-staging.domain.com`)"
      - "traefik.http.routers.minio-staging.entrypoints=websecure"
      - "traefik.http.routers.minio-staging.tls.certresolver=letsencrypt"
      - "traefik.http.routers.minio-staging.service=minio-staging"
      - "traefik.http.services.minio-staging.loadbalancer.server.port=9000"

      # ---- Web Console (port 9001) ----
      - "traefik.http.routers.minio-console-staging.rule=Host(`minio-console-staging.domain.com`)"
      - "traefik.http.routers.minio-console-staging.entrypoints=websecure"
      - "traefik.http.routers.minio-console-staging.tls.certresolver=letsencrypt"
      - "traefik.http.routers.minio-console-staging.service=minio-console-staging"
      - "traefik.http.services.minio-console-staging.loadbalancer.server.port=9001"
</code></pre>
<p>You also need an <code>entrypoint</code> for <code>:443</code> and a <code>certificatesresolver</code> named <code>letsencrypt</code>. Here's the minimum Traefik config (<code>traefik.staging.yml</code>):</p>
<pre><code class="language-yaml">api:
  dashboard: true

entryPoints:
  web:
    address: ":80"
  websecure:
    address: ":443"

certificatesResolvers:
  letsencrypt:
    acme:
      httpChallenge:
        entryPoint: web
      email: admin@domain.com
      storage: /etc/traefik/acme.json

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false
    network: proxy
</code></pre>
<p>Restart and watch the cert get issued:</p>
<pre><code class="language-bash">docker compose -f docker-compose.staging.yml up -d
docker compose -f docker-compose.staging.yml logs -f traefik | grep -i acme
</code></pre>
<p>Sanity check from your laptop:</p>
<pre><code class="language-bash">curl -I https://minio-staging.domain.com/minio/health/live
# HTTP/2 200
</code></pre>
<p>You can now log in to the <strong>web console</strong> at <code>https://minio-console-staging.domain.com</code> with <code>admin</code> / <code>change-me-please</code>.</p>
<p><strong>Important upload size tweak:</strong> if you're behind Cloudflare or NGINX in front of Traefik, raise the request body limit. Traefik itself has no default limit, but Cloudflare's free plan refuses anything over 100 MB. For self‑hosted edge proxies, set <code>client_max_body_size 0;</code> (NGINX) or the equivalent.</p>
<h2 id="heading-7-step-4-create-the-bucket-and-access-keys">7. Step 4 — Create the Bucket and Access Keys</h2>
<p>Anything that speaks S3 can talk to MinIO. The easiest tool is <code>mc</code> (the official MinIO client), shipped inside the same image.</p>
<h3 id="heading-71-connect-mc-to-your-server">7.1 Connect mc to your server</h3>
<pre><code class="language-bash">docker exec -it minio-staging \
  mc alias set local http://localhost:9000 admin change-me-please
</code></pre>
<h3 id="heading-72-create-a-bucket">7.2 Create a bucket</h3>
<pre><code class="language-bash">docker exec -it minio-staging mc mb local/domain-files-staging
</code></pre>
<h3 id="heading-73-choose-a-bucket-policy">7.3 Choose a bucket policy</h3>
<p>You have three choices, so just pick based on what you store:</p>
<table>
<thead>
<tr>
<th>Policy</th>
<th>When to use</th>
</tr>
</thead>
<tbody><tr>
<td><code>private</code> (default)</td>
<td>Anything sensitive — student transcripts, contracts, internal docs. Reads only via presigned URL.</td>
</tr>
<tr>
<td><code>download</code></td>
<td>Public read, no listing. Good for CDN‑style assets like avatars.</td>
</tr>
<tr>
<td><code>public</code></td>
<td>Anyone can read AND list. Use only for truly public content.</td>
</tr>
</tbody></table>
<p>Set one:</p>
<pre><code class="language-bash"># Private (recommended for documents)
docker exec -it minio-staging \
  mc anonymous set none local/domain-files-staging

# OR public read for static assets only:
docker exec -it minio-staging \
  mc anonymous set download local/domain-files-staging
</code></pre>
<h3 id="heading-74-create-a-dedicated-app-user-dont-use-root-keys">7.4 Create a dedicated app user (don't use root keys!)</h3>
<p>The <code>admin</code> account can wipe everything. Make a least‑privilege user for your app:</p>
<pre><code class="language-bash">docker exec -it minio-staging mc admin user add local \
  domain-app a-long-random-secret-key

# Attach the built-in read/write policy, scoped to one bucket via JSON:
cat &gt; /tmp/policy.json &lt;&lt;'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:*"],
      "Resource": [
        "arn:aws:s3:::domain-files-staging",
        "arn:aws:s3:::domain-files-staging/*"
      ]
    }
  ]
}
EOF

docker cp /tmp/policy.json minio-staging:/tmp/policy.json
docker exec -it minio-staging \
  mc admin policy create local domain-rw /tmp/policy.json
docker exec -it minio-staging \
  mc admin policy attach local domain-rw --user domain-app
</code></pre>
<p>Save those two values — they are your <code>S3_ACCESS_KEY</code> and <code>S3_SECRET_KEY</code>.</p>
<h2 id="heading-8-step-5-configure-your-app-to-use-minio-on-staging-only">8. Step 5 — Configure Your App to Use MinIO on Staging Only</h2>
<p>The trick to "MinIO in staging, real S3 in prod" is to use the <strong>same S3 client</strong> in your code and only swap the env vars.</p>
<p>Your <code>staging.env</code> (loaded by your staging compose stack):</p>
<pre><code class="language-env"># ---- Staging: self-hosted MinIO ----
STORAGE_ENABLED=true
S3_ENDPOINT=https://minio-staging.domain.com
S3_PUBLIC_ENDPOINT=https://minio-staging.domain.com
S3_BUCKET=domain-files-staging
S3_ACCESS_KEY=domain-app
S3_SECRET_KEY=a-long-random-secret-key
S3_REGION=us-east-1
S3_FORCE_PATH_STYLE=true
</code></pre>
<p>Your <code>production.env</code>:</p>
<pre><code class="language-env"># ---- Production: Cloudflare R2 ----
STORAGE_ENABLED=true
S3_ENDPOINT=https://&lt;account-id&gt;.r2.cloudflarestorage.com
S3_PUBLIC_ENDPOINT=https://files.domain.com
S3_BUCKET=domain-files
S3_ACCESS_KEY=&lt;r2-access-key&gt;
S3_SECRET_KEY=&lt;r2-secret-key&gt;
S3_REGION=auto
S3_FORCE_PATH_STYLE=true
</code></pre>
<p><code>S3_FORCE_PATH_STYLE=true</code> is critical for both MinIO <strong>and</strong> R2/Hetzner. Without it, the SDK tries <code>https://bucket.minio-staging.domain.com</code> (virtual‑host style), which won't resolve.</p>
<p>Now in your application code (Node.js example using AWS SDK v3):</p>
<pre><code class="language-javascript">// src/lib/s3.js
import { S3Client } from "@aws-sdk/client-s3";

export const s3 = new S3Client({
  endpoint: process.env.S3_ENDPOINT,
  region: process.env.S3_REGION,
  credentials: {
    accessKeyId: process.env.S3_ACCESS_KEY,
    secretAccessKey: process.env.S3_SECRET_KEY,
  },
  forcePathStyle: process.env.S3_FORCE_PATH_STYLE === "true",
});

export const BUCKET = process.env.S3_BUCKET;
export const PUBLIC_ENDPOINT = process.env.S3_PUBLIC_ENDPOINT;
</code></pre>
<p>The same <code>s3</code> instance now talks to MinIO on staging and to R2 in production with no code change.</p>
<h2 id="heading-9-step-6-upload-files-3-ways">9. Step 6 — Upload Files (3 Ways)</h2>
<h3 id="heading-91-from-a-server-best-for-trusted-backends">9.1 From a server (best for trusted backends)</h3>
<pre><code class="language-javascript">import { PutObjectCommand } from "@aws-sdk/client-s3";
import { s3, BUCKET } from "./lib/s3.js";
import { readFile } from "node:fs/promises";

export async function uploadDocument(localPath, key, contentType) {
  const Body = await readFile(localPath);
  await s3.send(new PutObjectCommand({
    Bucket: BUCKET,
    Key: key,
    Body,
    ContentType: contentType,
    // Optional: per-object metadata, useful for audits
    Metadata: { uploadedBy: "system", env: process.env.NODE_ENV },
  }));
  return key;
}
</code></pre>
<h3 id="heading-92-with-the-mc-cli-good-for-oneoff-uploads-migrations">9.2 With the mc CLI (good for one‑off uploads / migrations)</h3>
<pre><code class="language-bash">mc alias set staging https://minio-staging.domain.com domain-app a-long-random-secret-key
mc cp ./report.pdf staging/domain-files-staging/reports/2026/report.pdf
mc ls staging/domain-files-staging --recursive
</code></pre>
<h3 id="heading-93-directly-from-the-browser-via-a-presigned-put-url">9.3 Directly from the browser via a presigned PUT URL</h3>
<p>The recommended pattern for user uploads is: the file goes from the browser to MinIO with <strong>zero</strong> bytes touching your API server.</p>
<p>We'll cover this in detail next.</p>
<h2 id="heading-10-step-7-generate-presigned-urls-put-and-get">10. Step 7 — Generate Presigned URLs (PUT and GET)</h2>
<p>A <strong>presigned URL</strong> is a regular HTTPS URL with a time‑limited signature in the query string. Anyone with the URL can do exactly the action it was signed for (PUT this object, or GET that object) for the next N minutes — and nothing else.</p>
<p>This is what makes "users upload directly to storage" safe.</p>
<h3 id="heading-101-presigned-put-for-uploads">10.1 Presigned PUT (for uploads)</h3>
<pre><code class="language-javascript">// src/lib/presign.js
import { PutObjectCommand, GetObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { s3, BUCKET } from "./s3.js";
import { randomUUID } from "node:crypto";

export async function presignUpload({ filename, contentType, userId }) {
  const key = `users/\({userId}/\){randomUUID()}-${filename}`;
  const cmd = new PutObjectCommand({
    Bucket: BUCKET,
    Key: key,
    ContentType: contentType,
  });
  const uploadUrl = await getSignedUrl(s3, cmd, { expiresIn: 60 * 5 }); // 5 min
  return { uploadUrl, key };
}
</code></pre>
<p>Wire it to your API:</p>
<pre><code class="language-javascript">// POST /api/uploads/presign
app.post("/api/uploads/presign", requireAuth, async (req, res) =&gt; {
  const { filename, contentType } = req.body;
  const result = await presignUpload({
    filename,
    contentType,
    userId: req.user.id,
  });
  res.json(result); // { uploadUrl, key }
});
</code></pre>
<p>The browser uploads straight to MinIO:</p>
<pre><code class="language-javascript">// In your frontend
async function uploadFile(file) {
  const { uploadUrl, key } = await fetch("/api/uploads/presign", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ filename: file.name, contentType: file.type }),
  }).then(r =&gt; r.json());

  await fetch(uploadUrl, {
    method: "PUT",
    headers: { "Content-Type": file.type },
    body: file,
  });

  // Persist `key` in your DB so you can retrieve it later
  await fetch("/api/documents", {
    method: "POST",
    body: JSON.stringify({ key, originalName: file.name }),
  });
}
</code></pre>
<p>The <code>Content-Type</code> you send during PUT <strong>must match</strong> the one you signed with, or MinIO will reject the request with <code>SignatureDoesNotMatch</code>. This catches everyone the first time.</p>
<h3 id="heading-102-presigned-get-for-downloads">10.2 Presigned GET (for downloads)</h3>
<p>Same idea, but with <code>GetObjectCommand</code>:</p>
<pre><code class="language-javascript">export async function presignDownload(key, expiresIn = 60 * 10) {
  const cmd = new GetObjectCommand({ Bucket: BUCKET, Key: key });
  return getSignedUrl(s3, cmd, { expiresIn });
}
</code></pre>
<p>A typical "view document" endpoint:</p>
<pre><code class="language-javascript">app.get("/api/documents/:id/url", requireAuth, async (req, res) =&gt; {
  const doc = await db.documents.findById(req.params.id);
  if (!doc || !canUserSee(req.user, doc)) return res.sendStatus(403);
  const url = await presignDownload(doc.key, 600);
  res.json({ url });
});
</code></pre>
<p>The frontend just opens that URL — the file streams from MinIO directly to the user.</p>
<h3 id="heading-103-why-presigned-urls-beat-proxy-through-the-api">10.3 Why presigned URLs beat "proxy through the API"</h3>
<table>
<thead>
<tr>
<th></th>
<th>Proxy through API</th>
<th>Presigned URL</th>
</tr>
</thead>
<tbody><tr>
<td>Bytes through your app</td>
<td>All of them</td>
<td>Zero</td>
</tr>
<tr>
<td>API CPU/RAM cost</td>
<td>High</td>
<td>None</td>
</tr>
<tr>
<td>Throughput limit</td>
<td>Your API</td>
<td>MinIO's NIC</td>
</tr>
<tr>
<td>Auth check</td>
<td>Your code</td>
<td>Your code (still — check before signing)</td>
</tr>
</tbody></table>
<h2 id="heading-11-step-8-get-public-urls-for-documents">11. Step 8 — Get Public URLs for Documents</h2>
<p>Sometimes you want a permanent, unauthenticated URL — for example public profile pictures.</p>
<p>If the bucket policy allows anonymous reads (<code>mc anonymous set download …</code>), the public URL pattern is:</p>
<pre><code class="language-plaintext">https://minio-staging.domain.com/&lt;bucket&gt;/&lt;key&gt;
</code></pre>
<p>So <code>users/42/avatar.png</code> becomes:</p>
<pre><code class="language-plaintext">https://minio-staging.domain.com/domain-files-staging/users/42/avatar.png
</code></pre>
<p>In code:</p>
<pre><code class="language-javascript">export function publicUrl(key) {
  return `\({process.env.S3_PUBLIC_ENDPOINT}/\){BUCKET}/${key}`;
}
</code></pre>
<p>For <strong>private</strong> buckets (most documents), don't use public URLs at all — always go through <code>presignDownload(key)</code> so you can re‑check authorization on every request and expire links.</p>
<h2 id="heading-12-step-9-lock-down-cors-lifecycle-and-security">12. Step 9 — Lock Down CORS, Lifecycle, and Security</h2>
<h3 id="heading-121-allow-your-frontend-origins-cors">12.1 Allow your frontend origins (CORS)</h3>
<p>Browser uploads need CORS rules on the bucket. Drop this JSON via <code>mc</code>:</p>
<pre><code class="language-bash">cat &gt; /tmp/cors.json &lt;&lt;'EOF'
{
  "CORSRules": [
    {
      "AllowedOrigins": [
        "https://crm-staging.domain.com",
        "http://localhost:3000"
      ],
      "AllowedMethods": ["GET", "PUT", "POST", "HEAD"],
      "AllowedHeaders": ["*"],
      "ExposeHeaders": ["ETag"],
      "MaxAgeSeconds": 3000
    }
  ]
}
EOF

docker cp /tmp/cors.json minio-staging:/tmp/cors.json
docker exec -it minio-staging \
  mc cors set local/domain-files-staging /tmp/cors.json
</code></pre>
<h3 id="heading-122-autodelete-old-test-files-lifecycle">12.2 Auto‑delete old test files (lifecycle)</h3>
<p>Staging accumulates junk. Tell MinIO to expire anything older than 30 days:</p>
<pre><code class="language-bash">docker exec -it minio-staging \
  mc ilm rule add --expire-days 30 local/domain-files-staging
</code></pre>
<h3 id="heading-123-encrypt-at-rest">12.3 Encrypt at rest</h3>
<pre><code class="language-bash">docker exec -it minio-staging \
  mc encrypt set sse-s3 local/domain-files-staging
</code></pre>
<h3 id="heading-124-hard-rules">12.4 Hard rules</h3>
<ul>
<li><p><strong>Never</strong> ship <code>MINIO_ROOT_USER=admin</code> / <code>MINIO_ROOT_PASSWORD=admin123</code> to a server reachable from the internet. Generate strong values and store them in your secret manager.</p>
</li>
<li><p>The root account should be used only by <code>mc admin</code>, never by your app. The app uses a scoped IAM user (Step 7.4).</p>
</li>
<li><p>Keep the <strong>console</strong> subdomain behind an IP allow‑list or basic auth via Traefik middleware if it's truly public.</p>
</li>
<li><p>Rotate the app access keys at least every 90 days.</p>
</li>
</ul>
<h2 id="heading-13-step-10-backups-and-monitoring">13. Step 10 — Backups and Monitoring</h2>
<h3 id="heading-131-backups-mirror-to-a-cheap-cold-bucket-weekly">13.1 Backups: mirror to a cheap cold bucket weekly</h3>
<p>Set up a tiny cron job that uses <code>mc mirror</code> to push to Backblaze B2, R2, or another cheap S3 endpoint:</p>
<pre><code class="language-bash">mc alias set b2 https://s3.us-east-005.backblazeb2.com \(B2_KEY \)B2_SECRET
mc mirror --overwrite --remove \
  staging/domain-files-staging \
  b2/domain-staging-backup
</code></pre>
<p>Even at $6/TB/month this is essentially free for staging volumes.</p>
<h3 id="heading-132-monitoring-with-prometheus">13.2 Monitoring with Prometheus</h3>
<p>MinIO exposes Prometheus metrics out of the box at <code>/minio/v2/metrics/cluster</code>. Scrape with:</p>
<pre><code class="language-yaml">scrape_configs:
  - job_name: minio
    metrics_path: /minio/v2/metrics/cluster
    scheme: https
    static_configs:
      - targets: ["minio-staging.domain.com"]
</code></pre>
<p>If you have Grafana, import dashboard ID <strong>13502</strong> for an instant overview (capacity, request rates, latency, error counts).</p>
<h2 id="heading-14-troubleshooting-cheat-sheet">14. Troubleshooting Cheat Sheet</h2>
<table>
<thead>
<tr>
<th>Symptom</th>
<th>Likely cause</th>
<th>Fix</th>
</tr>
</thead>
<tbody><tr>
<td><code>SignatureDoesNotMatch</code> on presigned PUT</td>
<td>Browser sent a different <code>Content-Type</code> than what was signed</td>
<td>Send the exact same <code>Content-Type</code> header during PUT</td>
</tr>
<tr>
<td>Presigned URL works locally but not in browser</td>
<td><code>MINIO_SERVER_URL</code> not set, so URLs are signed for <code>minio:9000</code></td>
<td>Set <code>MINIO_SERVER_URL=https://minio-staging.domain.com</code> and restart</td>
</tr>
<tr>
<td><code>403 SignatureDoesNotMatch</code> after going through Cloudflare</td>
<td>Cloudflare strips/modifies headers</td>
<td>Set the DNS record to <strong>DNS‑only</strong> (gray cloud)</td>
</tr>
<tr>
<td><code>NoSuchBucket</code></td>
<td>App pointing at the wrong endpoint or bucket</td>
<td>Re‑check <code>S3_ENDPOINT</code> and <code>S3_BUCKET</code> in env</td>
</tr>
<tr>
<td>Browser CORS preflight fails</td>
<td>No CORS rule on the bucket</td>
<td>Apply the CORS JSON from §12.1</td>
</tr>
<tr>
<td>Upload works for small files, fails at 100 MB</td>
<td>Cloudflare free plan body limit</td>
<td>Use Cloudflare paid plan, or skip CF proxy</td>
</tr>
<tr>
<td><code>x509: certificate signed by unknown authority</code> from your app</td>
<td>App container doesn't trust Let's Encrypt</td>
<td>Update CA bundle (<code>apt install ca-certificates</code>) or use HTTP inside the Docker network</td>
</tr>
<tr>
<td>Web console redirects to <code>http://minio:9001/login</code></td>
<td><code>MINIO_BROWSER_REDIRECT_URL</code> missing</td>
<td>Set it to <code>https://minio-console-staging.domain.com</code></td>
</tr>
</tbody></table>
<p>Useful diagnostics:</p>
<pre><code class="language-bash"># Check MinIO health
curl -I https://minio-staging.domain.com/minio/health/live

# List all objects in a bucket
docker exec -it minio-staging mc ls --recursive local/domain-files-staging

# Tail MinIO logs
docker compose -f docker-compose.staging.yml logs -f minio

# Decode a presigned URL to see what it was signed for
echo "&lt;paste url&gt;" | tr '&amp;' '\n'
</code></pre>
<h2 id="heading-15-wrapping-up">15. Wrapping Up</h2>
<p>Here's what you have now:</p>
<ul>
<li><p>A free, S3‑compatible object store running on your own staging server.</p>
</li>
<li><p>Real HTTPS on a real domain (<code>https://minio-staging.domain.com</code>), thanks to Traefik + Let's Encrypt.</p>
</li>
<li><p>A scoped, least‑privilege application user — root keys stay locked away.</p>
</li>
<li><p>The same exact code paths in staging and production. Switching between MinIO / R2 / Hetzner / AWS S3 is a four‑variable change in the env file.</p>
</li>
<li><p>Presigned PUT URLs so users upload straight to storage, bypassing your API.</p>
</li>
<li><p>Presigned GET URLs so private documents are short‑lived and authorization‑gated.</p>
</li>
<li><p>Lifecycle rules that nuke old test files automatically.</p>
</li>
<li><p>Optional weekly mirror to a cold backup bucket.</p>
</li>
</ul>
<p>Production keeps running on managed storage where the SLA matters. Staging now costs you exactly <strong>$0 per month per gigabyte uploaded</strong> — and you can finally stop telling QA to "delete the test files when you're done."</p>
<h3 id="heading-further-reading">Further Reading</h3>
<ul>
<li><p><a href="https://min.io/docs/minio/container/index.html">MinIO Documentation</a></p>
</li>
<li><p><a href="https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-aws-sdk-s3-request-presigner/">AWS SDK v3 — <code>getSignedUrl</code></a></p>
</li>
<li><p><a href="https://doc.traefik.io/traefik/providers/docker/">Traefik v2 Docker provider</a></p>
</li>
<li><p><a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucket-policies.html">S3 bucket policy reference</a></p>
</li>
</ul>
<p>If this guide saved your team a few dollars, share it with another team that's still uploading test PDFs to a $90/month S3 bucket. Happy shipping.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Migrate to S3 Native State Locking in Terraform ]]>
                </title>
                <description>
                    <![CDATA[ If you've been running Terraform on AWS for any length of time, you know the setup: an S3 bucket for state storage, a DynamoDB table for state locking, and a handful of IAM policies tying them togethe ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-migrate-to-s3-native-state-locking-in-terraform/</link>
                <guid isPermaLink="false">69fd19239f93a850a430069b</guid>
                
                    <category>
                        <![CDATA[ Devops ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Terraform ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AWS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Cloud Computing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Infrastructure as code ]]>
                    </category>
                
                    <category>
                        <![CDATA[ S3 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tolani Akintayo ]]>
                </dc:creator>
                <pubDate>Thu, 07 May 2026 22:58:43 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/9619ad45-15c5-4be7-9221-ed4b76bc2b24.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>If you've been running Terraform on AWS for any length of time, you know the setup: an S3 bucket for state storage, a DynamoDB table for state locking, and a handful of IAM policies tying them together. It works. It has worked for years.</p>
<p>But it has always carried a cost that rarely gets discussed openly. That cost isn't just money, though a DynamoDB table with on-demand billing adds up across multiple teams and environments.</p>
<p>The real cost is complexity. Every new AWS environment needs both resources provisioned before Terraform can manage anything else. Every engineer who sets up their first Terraform backend has to understand why two completely different AWS services are responsible for what is logically one thing: storing and protecting state. And every incident involving a stuck lock has required someone to manually delete a record from DynamoDB to unblock the team.</p>
<p>In November 2024, AWS announced that S3 now supports native object locking for Terraform state files, meaning <strong>DynamoDB is no longer required for state locking</strong>. Terraform 1.10 added support for this feature, and it's now generally available.</p>
<p>In this tutorial, you'll learn:</p>
<ul>
<li><p>What S3 native locking is and how it works</p>
</li>
<li><p>How to set it up from scratch if you're starting a new project</p>
</li>
<li><p>How to migrate an existing S3 + DynamoDB setup to S3 native locking safely</p>
</li>
<li><p>How to verify locking is working and handle edge cases</p>
</li>
</ul>
<p>By the end, you'll have a simpler, cleaner Terraform backend with one fewer AWS resource to manage.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-what-is-terraform-state-locking">What Is Terraform State Locking?</a></p>
</li>
<li><p><a href="#heading-what-is-s3-native-state-locking">What Is S3 Native State Locking?</a></p>
</li>
<li><p><a href="#heading-how-s3-native-locking-compares-to-the-s3-dynamodb-approach">How S3 Native Locking Compares to the S3 + DynamoDB Approach</a></p>
</li>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-part-1-fresh-setup-how-to-configure-s3-native-locking-from-scratch">Part 1: Fresh Setup – How to Configure S3 Native Locking from Scratch</a></p>
<ul>
<li><p><a href="#heading-step-1-create-the-s3-bucket-with-versioning-and-encryption">Step 1: Create the S3 Bucket with Versioning and Encryption</a></p>
</li>
<li><p><a href="#heading-step-2-configure-the-terraform-backend-with-native-locking">Step 2: Configure the Terraform Backend with Native Locking</a></p>
</li>
<li><p><a href="#heading-step-3-initialize-and-verify">Step 3: Initialize and Verify</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-part-2-migration-how-to-move-from-s3-dynamodb-to-s3-native-locking">Part 2: Migration – How to Move from S3 + DynamoDB to S3 Native Locking</a></p>
<ul>
<li><p><a href="#heading-step-1-verify-your-current-setup">Step 1: Verify Your Current Setup</a></p>
</li>
<li><p><a href="#heading-step-2-enable-object-lock-on-the-existing-s3-bucket">Step 2: Enable Object Lock on the Existing S3 Bucket</a></p>
</li>
<li><p><a href="#heading-step-3-update-the-terraform-backend-configuration">Step 3: Update the Terraform Backend Configuration</a></p>
</li>
<li><p><a href="#heading-step-4-reinitialize-terraform">Step 4: Reinitialize Terraform</a></p>
</li>
<li><p><a href="#heading-step-5-verify-the-migration">Step 5: Verify the Migration</a></p>
</li>
<li><p><a href="#heading-step-6-clean-up-the-dynamodb-table">Step 6: Clean Up the DynamoDB Table</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-how-to-verify-that-locking-is-working">How to Verify That Locking Is Working</a></p>
</li>
<li><p><a href="#heading-how-to-handle-a-stuck-lock">How to Handle a Stuck Lock</a></p>
</li>
<li><p><a href="#heading-rollback-plan-if-something-goes-wrong">Rollback Plan: If Something Goes Wrong</a></p>
</li>
<li><p><a href="#heading-security-best-practices-for-your-state-bucket">Security Best Practices for Your State Bucket</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
<li><p><a href="#heading-references">References</a></p>
</li>
</ul>
<h2 id="heading-what-is-terraform-state-locking">What is Terraform State Locking?</h2>
<p>Before looking at the new approach, it helps to understand what state locking is solving.</p>
<p>Terraform stores everything it knows about your infrastructure in a <strong>state file</strong> – a JSON document that maps your configuration to real AWS resources. When you run <code>terraform apply</code>, Terraform reads this file, calculates the difference between the current state and your configuration, and makes the necessary changes.</p>
<p>The problem arises when two engineers or two CI/CD pipelines run and try to apply changes at the same time. If both read the state file simultaneously, calculate changes independently, and both try to write back, you get a <strong>race condition</strong>. The second write overwrites changes from the first, and your state is now out of sync with reality. This is a serious problem that can cause resources to be untracked, doubled, or destroyed unexpectedly.</p>
<p><strong>State locking</strong> solves this by creating a lock when any operation starts that could modify state. If a lock already exists, Terraform refuses to proceed and reports who holds the lock and when it was acquired. Only one operation can hold the lock at a time. When the operation completes, the lock is released.</p>
<pre><code class="language-plaintext">Terraform Run A                 State File / Lock                Terraform Run B
(User 1)                         (S3/DynamoDB)                   (User 2)

   |                                   |                            |
   |------- 1. Acquire Lock ----------&gt;|                            |
   |                                   |                            |
   |&lt;------ 2. Lock Granted -----------|                            |
   |                                   |                            |
   |                                   |------- 3. Acquire Lock ---&gt;|
   |            [PROCESSING]           |                            |
   |      (Modifying Infrastructure)   |&lt;------ 4. Lock Denied -----|
   |                                   |        (Wait / Retry)      |
   |                                   |                            |
   |------- 5. Release Lock ----------&gt;|                            |
   |                                   |                            |
   |           [COMPLETED]             |&lt;------ 6. Lock Granted ----|
   |                                   |                            |
   |                                   |       [PROCESSING]         |
   |                                   | (Modifying Infrastructure) |              
   |                                   |                            |
</code></pre>
<h2 id="heading-what-is-s3-native-state-locking">What Is S3 Native State Locking?</h2>
<p>Previously, Terraform's S3 backend used a DynamoDB table as the locking mechanism. When a lock was needed, Terraform wrote a record to DynamoDB with a <code>LockID</code> primary key. DynamoDB's conditional writes guaranteed that only one process could create that record, which is what made the locking atomic.</p>
<p>S3 native locking uses <strong>S3 Object Lock</strong> instead. S3 Object Lock is an S3 feature originally designed to enforce WORM (Write Once, Read Many) compliance for regulatory requirements. AWS extended this capability to support Terraform's state locking workflow.</p>
<p>When S3 native locking is enabled in your Terraform backend:</p>
<ol>
<li><p>Terraform writes your state to an <code>.tfstate</code> object in S3 (as before)</p>
</li>
<li><p>To acquire a lock, Terraform uses <strong>S3's conditional write operations</strong> – specifically the <code>if-none-match</code> conditional header to create a lock file atomically</p>
</li>
<li><p>If the lock file already exists, S3 rejects the write, and Terraform reports that a lock is held</p>
</li>
<li><p>When the operation completes, Terraform deletes the lock file to release the lock.</p>
</li>
</ol>
<p>The key difference from DynamoDB: the entire locking mechanism lives inside S3. No second service. No second set of IAM permissions. No second resource to provision.</p>
<p><strong>Note:</strong> This feature requires Terraform version <strong>1.10.0 or later</strong> and an S3 bucket with <strong>Object Lock enabled</strong>. Object Lock must be enabled at bucket creation time. You can't enable it on an existing bucket through the console or CLI. But there is a supported workaround for existing buckets, which we'll cover in Part 2.</p>
<h2 id="heading-how-s3-native-locking-compares-to-the-s3-dynamodb-approach">How S3 Native Locking Compares to the S3 + DynamoDB Approach</h2>
<table>
<thead>
<tr>
<th><strong>Aspect</strong></th>
<th><strong>S3 + DynamoDB (Old)</strong></th>
<th><strong>S3 Native Locking (New)</strong></th>
</tr>
</thead>
<tbody><tr>
<td><strong>AWS services required</strong></td>
<td>S3 + DynamoDB</td>
<td>S3 only</td>
</tr>
<tr>
<td><strong>IAM permissions needed</strong></td>
<td>S3 + DynamoDB permissions</td>
<td>S3 permissions only</td>
</tr>
<tr>
<td><strong>Terraform version</strong></td>
<td>Any</td>
<td>1.10.0 or later</td>
</tr>
<tr>
<td><strong>Setup complexity</strong></td>
<td>Two resources, two IAM scopes</td>
<td>One resource</td>
</tr>
<tr>
<td><strong>Stuck lock resolution</strong></td>
<td>Delete DynamoDB record</td>
<td>Delete S3 lock file</td>
</tr>
<tr>
<td><strong>Cost</strong></td>
<td>S3 storage + DynamoDB on-demand</td>
<td>S3 storage only</td>
</tr>
<tr>
<td><strong>Object Lock requirement</strong></td>
<td>Not required</td>
<td>Required on S3 bucket</td>
</tr>
<tr>
<td><strong>Locking mechanism</strong></td>
<td>DynamoDB conditional writes</td>
<td>S3 conditional writes (<code>if-none-match</code>)</td>
</tr>
<tr>
<td><strong>State versioning</strong></td>
<td>S3 Versioning (recommended)</td>
<td>S3 Versioning (required for full safety)</td>
</tr>
</tbody></table>
<p>The functional behavior from Terraform's perspective is identical. Locking works the same way. The lock information displayed when a lock is held has the same structure. The only difference is what happens under the hood.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before you start, make sure you have the following in place:</p>
<ul>
<li><strong>Terraform 1.10.0 or later</strong> installed. Check your version:</li>
</ul>
<pre><code class="language-shell">terraform version
</code></pre>
<p>If you need to upgrade, follow the <a href="https://developer.hashicorp.com/terraform/install">official upgrade guide</a>.</p>
<ul>
<li><strong>AWS CLI</strong> installed and configured with credentials that have permission to create and manage S3 buckets.</li>
</ul>
<pre><code class="language-shell">aws --version
aws sts get-caller-identity   # confirm you're authenticated
</code></pre>
<ul>
<li><p><strong>IAM permissions</strong> to perform the following S3 actions:</p>
<ul>
<li><p><code>s3:CreateBucket</code></p>
</li>
<li><p><code>s3:PutBucketVersioning</code></p>
</li>
<li><p><code>s3:PutBucketEncryption</code></p>
</li>
<li><p><code>s3:PutObjectLegalHold</code></p>
</li>
<li><p><code>s3:PutObjectRetention</code></p>
</li>
<li><p><code>s3:GetObject</code></p>
</li>
<li><p><code>s3:PutObject</code></p>
</li>
<li><p><code>s3:DeleteObject</code></p>
</li>
<li><p><code>s3:ListBucket</code></p>
</li>
</ul>
</li>
<li><p>For the <strong>migration path</strong>: access to your existing Terraform project and the S3 bucket and DynamoDB table currently in use.</p>
</li>
</ul>
<h2 id="heading-part-1-fresh-setup-how-to-configure-s3-native-locking-from-scratch">Part 1: Fresh Setup – How to Configure S3 Native Locking from Scratch</h2>
<p>Follow this section if you're starting a new Terraform project and want to use S3 native locking from the beginning.</p>
<h3 id="heading-step-1-create-the-s3-bucket-with-versioning-and-encryption">Step 1: Create the S3 Bucket with Versioning and Encryption</h3>
<p>Object Lock <strong>must be enabled at bucket creation time</strong>. You can't add it afterward through the standard console flow. Create the bucket using the AWS CLI with Object Lock enabled:</p>
<pre><code class="language-shell">aws s3api create-bucket \
  --bucket your-project-terraform-state \
  --region us-east-1 \
  --object-lock-enabled-for-bucket
</code></pre>
<p><strong>Note:</strong> For regions other than <code>us-east-1</code>, add the <code>--create-bucket-configuration</code> flag.</p>
<pre><code class="language-shell">aws s3api create-bucket \
  --bucket your-project-terraform-state \
  --region eu-west-1 \
  --create-bucket-configuration LocationConstraint=eu-west-1 \
  --object-lock-enabled-for-bucket
</code></pre>
<p>Now enable versioning on the bucket. Versioning is required alongside Object Lock and allows Terraform to recover previous state versions if something goes wrong:</p>
<pre><code class="language-shell">aws s3api put-bucket-versioning \
  --bucket your-project-terraform-state \
  --versioning-configuration Status=Enabled
</code></pre>
<p>Enable server-side encryption so your state files are encrypted at rest:</p>
<pre><code class="language-shell">aws s3api put-bucket-encryption \
  --bucket your-project-terraform-state \
  --server-side-encryption-configuration '{
    "Rules": [
      {
        "ApplyServerSideEncryptionByDefault": {
          "SSEAlgorithm": "AES256"
        },
        "BucketKeyEnabled": true
      }
    ]
  }'
</code></pre>
<p>Block all public access to the bucket. A Terraform state file contains resource IDs, IP addresses, and potentially sensitive values. It should never be publicly accessible:</p>
<pre><code class="language-shell">aws s3api put-public-access-block \
  --bucket your-project-terraform-state \
  --public-access-block-configuration \
    "BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"
</code></pre>
<p>Verify the bucket configuration:</p>
<pre><code class="language-shell"># Confirm Object Lock is enabled
aws s3api get-object-lock-configuration \
  --bucket your-project-terraform-state
 
# Confirm versioning is enabled
aws s3api get-bucket-versioning \
  --bucket your-project-terraform-state
 
# Confirm encryption is configured
aws s3api get-bucket-encryption \
  --bucket your-project-terraform-state
</code></pre>
<p>Expected output for the Object Lock check:</p>
<pre><code class="language-json">{
    "ObjectLockConfiguration": {
        "ObjectLockEnabled": "Enabled"
    }
}
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/65a5bfab4c73b29396c0b895/2b2e56cf-687f-4932-a61e-ed7cc33ea6f1.png" alt="Terminal showing AWS CLI verification commands confirming S3 bucket is configured correctly with Object Lock, versioning, and encryption enabled" style="display:block;margin:0 auto" width="1120" height="616" loading="lazy">

<h3 id="heading-step-2-configure-the-terraform-backend-with-native-locking">Step 2: Configure the Terraform Backend with Native Locking</h3>
<p>In your Terraform project, create or update your <code>backend.tf</code> file:</p>
<pre><code class="language-hcl">terraform {
  backend "s3" {
    bucket = "your-project-terraform-state"
    key    = "production/terraform.tfstate"
    region = "us-east-1"
 
    # Enable S3 native state locking
    # Requires Terraform 1.10.0+ and a bucket with Object Lock enabled
    use_lockfile = true
 
    # Encryption at rest
    encrypt = true
  }
}
</code></pre>
<p>The critical difference from the old configuration is the <code>use_lockfile = true</code> parameter. Notice what is <strong>absent</strong>: there's no <code>dynamodb_table</code> argument. No DynamoDB table. No second service.</p>
<p>Here's a direct comparison of the old and new configurations:</p>
<p><strong>Old configuration (S3 + DynamoDB):</strong></p>
<pre><code class="language-hcl">terraform {
  backend "s3" {
    bucket         = "your-project-terraform-state"
    key            = "production/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "terraform-state-lock"   # this goes away
  }
}
</code></pre>
<p><strong>New configuration (S3 native locking):</strong></p>
<pre><code class="language-hcl">terraform {
  backend "s3" {
    bucket       = "your-project-terraform-state"
    key          = "production/terraform.tfstate"
    region       = "us-east-1"
    encrypt      = true
    use_lockfile = true   # this replaces dynamodb_table
  }
}
</code></pre>
<h3 id="heading-step-3-initialize-and-verify">Step 3: Initialize and Verify</h3>
<p>Run <code>terraform init</code> to initialize the backend:</p>
<pre><code class="language-shell">terraform init
</code></pre>
<p>Expected output:</p>
<pre><code class="language-plaintext">Initializing the backend...
 
Successfully configured the backend "s3"! Terraform will automatically
use this backend unless the backend configuration changes.
 
Initializing provider plugins...
 
Terraform has been successfully initialized!
</code></pre>
<p>Run a plan to confirm everything is working end-to-end:</p>
<pre><code class="language-shell">terraform plan
</code></pre>
<p>If locking is working, you'll see a brief pause while Terraform acquires the lock before the plan output appears. You'll also see the lock information if you look at the S3 bucket&nbsp;– a <code>.tflock</code> file will appear temporarily alongside your state file during the operation and disappear when it completes.</p>
<h2 id="heading-part-2-migration-how-to-move-from-s3-dynamodb-to-s3-native-locking">Part 2: Migration&nbsp;– How to Move from S3 + DynamoDB to S3 Native Locking</h2>
<p>Follow this section if you have an <strong>existing Terraform setup</strong> using an S3 bucket and DynamoDB table for state locking, and you want to migrate to S3 native locking.</p>
<p><strong>Important:</strong> Migration requires a maintenance window or at minimum a period where no Terraform operations are running. You're changing the backend configuration, which means <strong>all team members and CI/CD pipelines must stop running</strong> <code>terraform plan</code> <strong>or</strong> <code>terraform apply</code> <strong>during the migration</strong>. The migration itself takes under 10 minutes.</p>
<h3 id="heading-step-1-verify-your-current-setup">Step 1: Verify Your Current Setup</h3>
<p>Before making any changes, document your existing backend configuration and confirm the state file is accessible:</p>
<pre><code class="language-shell"># Confirm your state file is in S3
aws s3 ls s3://your-existing-bucket/path/to/terraform.tfstate
 
# Confirm the DynamoDB table exists
aws dynamodb describe-table \
  --table-name your-dynamodb-lock-table \
  --query 'Table.TableStatus'
</code></pre>
<p>Check your current <code>backend.tf</code> and note the exact values:</p>
<pre><code class="language-shell"># Your current backend.tf - note these values before changing anything
terraform {
  backend "s3" {
    bucket         = "your-existing-bucket"       # note this
    key            = "path/to/terraform.tfstate"   # note this
    region         = "us-east-1"                   # note this
    encrypt        = true
    dynamodb_table = "your-dynamodb-lock-table"    # this will be removed
  }
}
</code></pre>
<p>Run one final plan to confirm the current state is clean and there are no unexpected changes pending:</p>
<pre><code class="language-shell">terraform plan
</code></pre>
<p>If the plan shows no changes, you're in a safe state to proceed.</p>
<h3 id="heading-step-2-enable-object-lock-on-the-existing-s3-bucket">Step 2: Enable Object Lock on the Existing S3 Bucket</h3>
<p>This is the most important step in the migration. Object Lock can't normally be enabled on an existing bucket. It's a setting that must be configured at creation time.</p>
<p>But AWS provides a way to enable Object Lock on an existing bucket through a support request or through a direct API call that's not exposed in the standard console UI. AWS has officially documented this path for the Terraform migration use case.</p>
<p>Run the following AWS CLI command to enable Object Lock on your <strong>existing</strong> bucket:</p>
<pre><code class="language-bash">aws s3api put-object-lock-configuration \
  --bucket your-existing-bucket \
  --object-lock-configuration '{"ObjectLockEnabled": "Enabled"}'
</code></pre>
<p><strong>Note:</strong> This command enables Object Lock in <strong>governance mode with no default retention</strong>, meaning it enables the locking capability without setting a default retention period on all objects. This is exactly what Terraform's native locking needs: the ability to create and delete lock files, not permanent object retention.</p>
<p>Verify Object Lock is now enabled:</p>
<pre><code class="language-shell">aws s3api get-object-lock-configuration \
  --bucket your-existing-bucket
</code></pre>
<p>Expected output:</p>
<pre><code class="language-json">{
    "ObjectLockConfiguration": {
        "ObjectLockEnabled": "Enabled"
    }
}
</code></pre>
<p>Also verify that versioning is already enabled (it should be if you are running a production Terraform setup):</p>
<pre><code class="language-shell">aws s3api get-bucket-versioning \
  --bucket your-existing-bucket
</code></pre>
<p>Expected output:</p>
<pre><code class="language-json">{
    "Status": "Enabled"
}
</code></pre>
<p>If versioning isn't enabled, enable it before proceeding:</p>
<pre><code class="language-shell">aws s3api put-bucket-versioning \
  --bucket your-existing-bucket \
  --versioning-configuration Status=Enabled
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/65a5bfab4c73b29396c0b895/cd17df01-3d0a-4f93-9250-3f51627e91c8.png" alt="Terminal output showing successful Object Lock enablement on an existing S3 bucket using the AWS CLI" style="display:block;margin:0 auto" width="1204" height="320" loading="lazy">

<h3 id="heading-step-3-update-the-terraform-backend-configuration">Step 3: Update the Terraform Backend Configuration</h3>
<p>Update your <code>backend.tf</code> to remove the <code>dynamodb_table</code> argument and add <code>use_lockfile = true</code>:</p>
<pre><code class="language-hcl">terraform {
  backend "s3" {
    bucket = "your-existing-bucket"
    key    = "path/to/terraform.tfstate"
    region = "us-east-1"
    encrypt = true
 
    # Add this:
    use_lockfile = true
 
    # Remove this line entirely:
    # dynamodb_table = "your-dynamodb-lock-table"
  }
}
</code></pre>
<p>Your updated <code>backend.tf</code> should look like this:</p>
<pre><code class="language-hcl">terraform {
  backend "s3" {
    bucket       = "your-existing-bucket"
    key          = "path/to/terraform.tfstate"
    region       = "us-east-1"
    encrypt      = true
    use_lockfile = true
  }
}
</code></pre>
<h3 id="heading-step-4-reinitialize-terraform">Step 4: Reinitialize Terraform</h3>
<p>Run <code>terraform init</code> with the <code>-reconfigure</code> flag. This flag tells Terraform that the backend configuration has changed intentionally and to reinitialize without prompting you to copy state (the state is already in the same bucket):</p>
<pre><code class="language-shell">terraform init -reconfigure
</code></pre>
<p>Expected output:</p>
<pre><code class="language-plaintext">Initializing the backend...
 
Successfully configured the backend "s3"! Terraform will automatically
use this backend unless the backend configuration changes.
 
Initializing provider plugins...
- Reusing previous version of hashicorp/aws from the dependency lock file
 
Terraform has been successfully initialized!
</code></pre>
<p><strong>If you see an error here:</strong> The most common cause is that Object Lock wasn't successfully enabled on the bucket. Re-run the verification from Step 2 before proceeding.</p>
<h3 id="heading-step-5-verify-the-migration">Step 5: Verify the Migration</h3>
<p>Run a plan to confirm Terraform is working correctly with the new backend configuration:</p>
<pre><code class="language-shell">terraform plan
</code></pre>
<p>The plan should:</p>
<ul>
<li><p>Complete successfully</p>
</li>
<li><p>Show the same result as the plan you ran in Step 1 (no changes, or the same changes as before)</p>
</li>
<li><p>NOT mention DynamoDB anywhere in its output</p>
</li>
</ul>
<p>To confirm that locking is actually using S3 instead of DynamoDB, open a second terminal and run a plan while the first one is running. You should see the second terminal output a lock error that mentions S3, not DynamoDB:</p>
<pre><code class="language-plaintext">╷
│ Error: Error acquiring the state lock
│
│Error message: operation error S3: PutObject, https response       error StatusCode: 409,
│ RequestID: ..., api error Conflict: Object lock already exists for this key.
│
│ Lock Info:
│   ID:        a1b2c3d4-e5f6-7890-abcd-ef1234567890
│   Path:      your-existing-bucket/path/to/terraform.tfstate.tflock
│   Operation: OperationTypePlan
│   Who:       user@hostname
│   Version:   1.10.0
│   Created:   2026-05-06 14:22:01 UTC
│   Info:
╵
</code></pre>
<p>The <code>Path</code> field shows <code>.tfstate.tflock</code>, a file in your S3 bucket, not a DynamoDB record. This confirms that locking is now handled entirely by S3.</p>
<img src="https://cdn.hashnode.com/uploads/covers/65a5bfab4c73b29396c0b895/e9abb703-af6e-429c-83bb-2ea2dac43a3a.png" alt="Two terminals showing concurrent terraform plan commands, the second one displays a lock error confirming S3 native locking is working" style="display:block;margin:0 auto" width="1264" height="539" loading="lazy">

<h3 id="heading-step-6-clean-up-the-dynamodb-table">Step 6: Clean Up the DynamoDB Table</h3>
<p>Once you've confirmed the migration is working correctly and your team has run at least one successful <code>plan</code> and <code>apply</code> cycle using the new backend, you can remove the DynamoDB table.</p>
<p><strong>Wait at least 24-48 hours before deleting the DynamoDB table</strong> if you have CI/CD pipelines or multiple team members. This gives time to catch any pipeline that wasn't updated with the new backend configuration.</p>
<p>When you're ready, delete the DynamoDB table:</p>
<pre><code class="language-shell">aws dynamodb delete-table \
  --table-name your-dynamodb-lock-table
</code></pre>
<p>Confirm the deletion:</p>
<pre><code class="language-shell">aws dynamodb describe-table \
  --table-name your-dynamodb-lock-table
</code></pre>
<p>Expected output:</p>
<pre><code class="language-plaintext">An error occurred (ResourceNotFoundException) when calling the DescribeTable operation:
Requested resource not found
</code></pre>
<p>This error confirms that the table is gone. The migration is complete.</p>
<p>If you provisioned the DynamoDB table using Terraform (which is the recommended pattern), remove the resource from your Terraform configuration and run <code>terraform apply</code> to destroy it via Terraform rather than the CLI directly. This keeps your state clean:</p>
<pre><code class="language-hcl"># Remove this entire block from your Terraform configuration:
resource "aws_dynamodb_table" "terraform_state_lock" {
  name         = "terraform-state-lock"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "LockID"
 
  attribute {
    name = "LockID"
    type = "S"
  }
}
</code></pre>
<p>After removing the block, run:</p>
<pre><code class="language-bash">terraform apply
</code></pre>
<p>Terraform will detect that the DynamoDB table resource has been removed from configuration and will destroy the table.</p>
<h2 id="heading-how-to-verify-that-locking-is-working">How to Verify That Locking Is Working</h2>
<p>After completing either the fresh setup or the migration, use this procedure to independently verify that locking is functioning correctly.</p>
<h3 id="heading-method-1-observe-the-lock-file-during-an-operation">Method 1: Observe the lock file during an operation</h3>
<p>In one terminal, start a long-running plan against a configuration with many resources:</p>
<pre><code class="language-shell">terraform plan
</code></pre>
<p>While it's running, in a second terminal, check for the lock file in S3:</p>
<pre><code class="language-shell">aws s3 ls s3://your-bucket/path/to/ | grep tflock
</code></pre>
<p>You should see a file like:</p>
<pre><code class="language-plaintext">2026-05-06 14:22:01        512 terraform.tfstate.tflock
</code></pre>
<p>After the plan completes, run the same command again. The <code>.tflock</code> file should be gone.</p>
<h3 id="heading-method-2-read-the-lock-file-contents">Method 2: Read the lock file contents</h3>
<p>While a plan is running, download and read the lock file to see its contents:</p>
<pre><code class="language-shell">aws s3 cp \
  s3://your-bucket/path/to/terraform.tfstate.tflock \
  /tmp/current.lock &amp;&amp; cat /tmp/current.lock
</code></pre>
<p>Expected output (formatted for readability):</p>
<pre><code class="language-json">{
  "ID": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "Operation": "OperationTypePlan",
  "Info": "",
  "Who": "tolani@dev-machine",
  "Version": "1.10.0",
  "Created": "2026-05-06T14:22:01.123456789Z",
  "Path": "your-bucket/path/to/terraform.tfstate"
}
</code></pre>
<p>This is the same lock information that Terraform displays when a lock is held. It's now a JSON file in S3 rather than a record in DynamoDB.</p>
<h2 id="heading-how-to-handle-a-stuck-lock">How to Handle a Stuck Lock</h2>
<p>With the DynamoDB backend, resolving a stuck lock meant deleting a record from the DynamoDB table. With S3 native locking, it means deleting the <code>.tflock</code> file from S3.</p>
<p>A lock can get stuck if:</p>
<ul>
<li><p>A <code>terraform apply</code> or <code>plan</code> process was killed mid-execution</p>
</li>
<li><p>A CI/CD pipeline runner crashed during a Terraform operation</p>
</li>
<li><p>A network interruption prevented the lock release from completing</p>
</li>
</ul>
<p>Here's how you can check for a stuck lock:</p>
<pre><code class="language-shell">aws s3 ls s3://your-bucket/path/to/ | grep tflock
</code></pre>
<p>If a <code>.tflock</code> file exists and no Terraform operation is currently running, it is a stuck lock.</p>
<p>You can also read the lock to understand who held it:</p>
<pre><code class="language-shell">aws s3 cp \
  s3://your-bucket/path/to/terraform.tfstate.tflock \
  /tmp/stuck.lock &amp;&amp; cat /tmp/stuck.lock
</code></pre>
<p>This tells you who (<code>Who</code> field) was running the operation, what operation it was (<code>Operation</code> field), and when it was acquired (<code>Created</code> field).</p>
<p>And you can force-unlock using Terraform like this:</p>
<pre><code class="language-shell">terraform force-unlock LOCK-ID
</code></pre>
<p>Replace <code>LOCK-ID</code> with the <code>ID</code> value from the lock file contents. For example:</p>
<pre><code class="language-shell">terraform force-unlock a1b2c3d4-e5f6-7890-abcd-ef1234567890
</code></pre>
<p>Terraform will confirm:</p>
<pre><code class="language-plaintext">Do you really want to force-unlock?
  Terraform will remove the lock on the remote state.
  This will allow local Terraform commands to modify this state, even though it
  may be still be in use. Only 'yes' will be accepted to confirm.
 
  Enter a value: yes
 
Terraform state has been successfully unlocked!
</code></pre>
<p>An alternative is to delete the lock file directly via CLI. If <code>terraform force-unlock</code> doesn't work (for example, because you are running in a CI environment without Terraform available), delete the lock file directly:</p>
<pre><code class="language-shell">aws s3 rm s3://your-bucket/path/to/terraform.tfstate.tflock
</code></pre>
<p><strong>Only delete the lock file if you are certain no Terraform operation is currently running.</strong> Deleting a lock that is actively held by a running operation will allow a second concurrent operation to start, which is exactly the race condition locking is designed to prevent.</p>
<h2 id="heading-rollback-plan-if-something-goes-wrong">Rollback Plan: If Something Goes Wrong</h2>
<p>If you encounter problems after migrating, you can roll back to the S3 + DynamoDB setup with these steps.</p>
<p><strong>Step 1: Stop all Terraform operations</strong> in your team and CI/CD pipelines.</p>
<p><strong>Step 2: Recreate the DynamoDB table</strong> if you already deleted it:</p>
<pre><code class="language-shell">aws dynamodb create-table \
  --table-name terraform-state-lock \
  --attribute-definitions AttributeName=LockID,AttributeType=S \
  --key-schema AttributeName=LockID,KeyType=HASH \
  --billing-mode PAY_PER_REQUEST
</code></pre>
<p><strong>Step 3: Revert</strong> <code>backend.tf</code> to the previous configuration:</p>
<pre><code class="language-hcl">terraform {
  backend "s3" {
    bucket         = "your-existing-bucket"
    key            = "path/to/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "terraform-state-lock"   # restored
    # Remove: use_lockfile = true
  }
}
</code></pre>
<p><strong>Step 4: Reinitialize:</strong></p>
<pre><code class="language-shell">terraform init -reconfigure
</code></pre>
<p><strong>Step 5: Verify:</strong></p>
<pre><code class="language-shell">terraform plan
</code></pre>
<p>The state file hasn't moved, so there's no data loss during a rollback. The only change is which locking mechanism Terraform uses.</p>
<p><strong>Note:</strong> Object Lock being enabled on the S3 bucket doesn't prevent the rollback. Object Lock and DynamoDB locking can coexist, Object Lock simply adds a capability to the bucket. Using <code>dynamodb_table</code> in your backend config tells Terraform to use DynamoDB regardless of whether Object Lock is enabled on the bucket.</p>
<h2 id="heading-security-best-practices-for-your-state-bucket">Security Best Practices for Your State Bucket</h2>
<p>Migrating to S3 native locking is a good opportunity to review the overall security configuration of your state bucket. Here are the practices every production Terraform state bucket should implement:</p>
<h3 id="heading-enable-versioning-required">Enable Versioning (Required)</h3>
<p>Versioning is a hard requirement for S3 native locking to work safely. It ensures that if a state file is accidentally overwritten or corrupted, you can restore a previous version.</p>
<pre><code class="language-shell">aws s3api put-bucket-versioning \
  --bucket your-state-bucket \
  --versioning-configuration Status=Enabled
</code></pre>
<h3 id="heading-block-all-public-access-non-negotiable">Block All Public Access (Non-Negotiable)</h3>
<p>Your state file contains resource ARNs, IP addresses, and may contain sensitive values passed through Terraform variables. It must never be publicly accessible.</p>
<pre><code class="language-shell">aws s3api put-public-access-block \
  --bucket your-state-bucket \
  --public-access-block-configuration \
    "BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"
</code></pre>
<h3 id="heading-enable-server-side-encryption">Enable Server-Side Encryption</h3>
<p>Always encrypt state files at rest. AES256 is the minimum. If your organization requires KMS key management:</p>
<pre><code class="language-shell">aws s3api put-bucket-encryption \
  --bucket your-state-bucket \
  --server-side-encryption-configuration '{
    "Rules": [
      {
        "ApplyServerSideEncryptionByDefault": {
          "SSEAlgorithm": "aws:kms",
          "KMSMasterKeyID": "arn:aws:kms:us-east-1:123456789012:key/your-kms-key-id"
        },
        "BucketKeyEnabled": true
      }
    ]
  }'
</code></pre>
<h3 id="heading-apply-least-privilege-iam-permissions">Apply Least-Privilege IAM Permissions</h3>
<p>The role or user that Terraform uses to access the state bucket should have only the permissions it needs. Here's a minimal IAM policy for S3 native locking:</p>
<pre><code class="language-json">{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "TerraformStateAccess",
      "Effect": "Allow",
      "Action": [
        "s3:ListBucket",
        "s3:GetObject",
        "s3:PutObject",
        "s3:DeleteObject"
      ],
      "Resource": [
        "arn:aws:s3:::your-state-bucket",
        "arn:aws:s3:::your-state-bucket/*"
      ]
    },
    {
      "Sid": "TerraformStateLocking",
      "Effect": "Allow",
      "Action": [
        "s3:GetObjectLegalHold",
        "s3:PutObjectLegalHold",
        "s3:GetObjectRetention",
        "s3:PutObjectRetention"
      ],
      "Resource": "arn:aws:s3:::your-state-bucket/*.tflock"
    }
  ]
}
</code></pre>
<p>Notice what is absent: there are no DynamoDB permissions. This is a cleaner, smaller permission set than the old approach required.</p>
<h3 id="heading-enable-access-logging">Enable Access Logging</h3>
<p>Log all access to your state bucket in CloudTrail or S3 server access logs. This gives you an audit trail of every time state was read, written, or locked:</p>
<pre><code class="language-shell">aws s3api put-bucket-logging \
  --bucket your-state-bucket \
  --bucket-logging-status '{
    "LoggingEnabled": {
      "TargetBucket": "your-logging-bucket",
      "TargetPrefix": "terraform-state-access/"
    }
  }'
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>AWS S3 native state locking removes the need for a DynamoDB table from your Terraform backend setup. The result is simpler infrastructure, a smaller IAM permission surface, and one fewer service to provision, monitor, and pay for across every environment your team manages.</p>
<p>Here's a summary of what you accomplished:</p>
<ul>
<li><p>Understood what state locking is and why it's required for safe Terraform operations</p>
</li>
<li><p>Compared S3 native locking to the existing S3 + DynamoDB approach</p>
</li>
<li><p>Set up a fresh Terraform backend using S3 native locking with correct bucket configuration</p>
</li>
<li><p>Migrated an existing backend from S3 + DynamoDB to S3 native locking safely</p>
</li>
<li><p>Learned how to verify locking, handle stuck locks, and roll back if needed</p>
</li>
<li><p>Applied security best practices to the state bucket</p>
</li>
</ul>
<p>This pattern – using S3 native locking – is the recommended approach for all new Terraform projects on AWS going forward. If you're managing a large estate with multiple Terraform backends, consider automating the migration using a script or Terraform module that applies the pattern across all your state buckets.</p>
<p><em>If you are building or optimizing cloud infrastructure for a startup and want a complete reference for production-ready Terraform modules, CI/CD pipeline patterns, and infrastructure runbooks, check out</em> <a href="https://coachli.co/tolani-akintayo/PR-H4oQS">The Startup DevOps Field Guide</a><em>. It covers the full lifecycle of AWS infrastructure from initial setup to production reliability.</em></p>
<h2 id="heading-references">References</h2>
<ul>
<li><p><a href="https://developer.hashicorp.com/terraform/language/backend/s3#use_lockfile">HashiCorp - S3 Backend Configuration: use_lockfile</a></p>
</li>
<li><p><a href="https://github.com/hashicorp/terraform/releases/tag/v1.10.0">HashiCorp: Terraform 1.10 Release Notes</a></p>
</li>
<li><p><a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-lock.html">AWS Docs: S3 Object Lock Overview</a></p>
</li>
<li><p><a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObjectLockConfiguration.html">AWS Docs: PutObjectLockConfiguration API</a></p>
</li>
<li><p><a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/conditional-requests.html">AWS Docs: S3 Conditional Writes</a></p>
</li>
<li><p><a href="https://developer.hashicorp.com/terraform/language/state/locking">HashiCorp: Backend State Locking</a></p>
</li>
<li><p><a href="https://developer.hashicorp.com/terraform/cli/commands/force-unlock">HashiCorp: terraform force-unlock Command</a></p>
</li>
<li><p><a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/manage-versioning-examples.html">AWS Docs: Enabling S3 Versioning</a></p>
</li>
<li><p><a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/serv-side-encryption.html">AWS Docs: S3 Server-Side Encryption</a></p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Host a Static Website on AWS S3 and CloudFront ]]>
                </title>
                <description>
                    <![CDATA[ DevOps might seem like a complex field with various specializations and tools. In this article, I’ll simplify one key aspect by demonstrating how to host a static website using Amazon S3 (Simple Storage Service) and CloudFront, AWS’s Content Delivery... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/host-a-static-website-on-aws-s3-and-cloudfront/</link>
                <guid isPermaLink="false">67e2cd0841be93b03e257b93</guid>
                
                    <category>
                        <![CDATA[ AWS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ S3 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ cloudfront ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Cloud Computing ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Oghenekparobo Stephen ]]>
                </dc:creator>
                <pubDate>Tue, 25 Mar 2025 15:34:32 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1742916792332/9dbf6a18-7260-434f-815d-e38a82c9e47e.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>DevOps might seem like a complex field with various specializations and tools. In this article, I’ll simplify one key aspect by demonstrating how to host a static website using Amazon S3 (Simple Storage Service) and CloudFront, AWS’s Content Delivery Network (CDN) that caches and distributes content efficiently for faster access.</p>
<p>To follow along, you should have:</p>
<ul>
<li><p>An <a target="_blank" href="https://aws.amazon.com/free/"><strong>AWS account</strong></a><strong>.</strong></p>
</li>
<li><p>A basic understanding of AWS architecture (You can gain insights <a target="_blank" href="https://youtu.be/NhDYbskXRgc?si=mZi-dN4AbdVZtWD5">here</a>.)</p>
</li>
<li><p>Knowledge of <a target="_blank" href="https://aws.amazon.com/iam/"><strong>AWS IAM</strong></a> (Identity and Access Management for secure resource access control)</p>
</li>
<li><p>Familiarity with <a target="_blank" href="https://youtu.be/RGOj5yH7evk?si=AXlPrk1czT9zm4ql"><strong>Git and GitHub</strong></a></p>
</li>
</ul>
<p>Let’s dive in and set up our static site hosting step by step.</p>
<h2 id="heading-table-of-contents">Table of Contents:</h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-what-is-aws-s3">What is AWS S3?</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-fine-grained-access-controls-in-s3">Fine-Grained Access Controls in S3</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-encryption-at-rest-in-s3">Encryption at Rest in S3</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-upload-a-static-website-to-amazon-s3">How to Upload a Static Website to Amazon S3</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-a-bucket">What is a Bucket?</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-how-to-create-a-bucket">How to Create a Bucket</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-upload-files-and-folders-to-an-s3-bucket">How to Upload Files and Folders to an S3 Bucket</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-set-permissions-and-properties-in-aws-s3">How to Set Permissions and Properties in AWS S3</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-a-bucket-policy">What is a Bucket Policy?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-set-up-a-bucket-policy">How to Set Up a Bucket Policy</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-enable-static-hosting">How to Enable Static Hosting</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-amazon-cloudfront">Amazon CloudFront</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-key-features-of-amazon-cloudfront">Key Features of Amazon CloudFront</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-why-amazon-s3-alone-is-not-enough">Why Amazon S3 Alone is Not Enough</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-why-you-should-serve-your-website-with-cloudfront">Why You Should Serve Your Website with CloudFront</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-a-cloudfront-distribution">What is a CloudFront Distribution?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-understanding-the-amazon-resource-name-arn">Understanding the Amazon Resource Name (ARN)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-updated-s3-bucket-policy">Updated S3 Bucket Policy</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ol>
<h2 id="heading-what-is-aws-s3">What is AWS S3?</h2>
<p>Amazon Simple Storage Service (Amazon S3) is an object storage service designed to store and retrieve any amount of data from anywhere.</p>
<p>Using Amazon S3 is straightforward. You start by selecting a region, creating a storage container called a "bucket," and then uploading your data. There is no limit to how much data you can store, and you can retrieve it at any time.</p>
<p>Amazon S3 automatically creates backups of your data by storing copies across multiple devices. It also allows you to preserve different versions of your files, helping you recover data if it’s accidentally lost. If you delete a file by mistake, you can restore it using Amazon S3’s versioning feature.</p>
<p>Amazon S3 offers configurable lifecycle policies to help manage data efficiently throughout its lifecycle. Security is a top priority for AWS, ensuring that data uploads and retrievals are protected using SSL encryption for secure transmission. AWS also provides multiple security features to safeguard your data, including fine-grained access controls and encryption at rest. Let’s explore these two features in a bit more detail.</p>
<h3 id="heading-fine-grained-access-controls-in-s3"><strong>Fine-Grained Access Controls in S3</strong></h3>
<p>Amazon S3 provides fine-grained access control, allowing you to define who can access your data and what actions they can perform. This is managed through:</p>
<ol>
<li><p><strong>AWS Identity and Access Management (IAM)</strong>: Controls user permissions at the AWS account level. You can grant specific users or roles permissions to access S3.</p>
</li>
<li><p><strong>S3 bucket policies</strong>: JSON-based policies applied at the bucket level to control access for all objects in a bucket.</p>
</li>
<li><p><strong>Access Control Lists (ACLs)</strong>: Defines permissions for individual objects within a bucket (less commonly used since bucket policies are more powerful).</p>
</li>
<li><p><strong>Block public access settings</strong>: Prevents accidental public exposure of S3 data by restricting open access.</p>
</li>
</ol>
<h3 id="heading-encryption-at-rest-in-s3"><strong>Encryption at Rest in S3</strong></h3>
<p>Encryption at rest ensures that stored data remains secure, even if unauthorized access occurs. S3 supports multiple encryption options:</p>
<ol>
<li><p><strong>Server-Side Encryption (SSE):</strong></p>
<ul>
<li><p><strong>SSE-S3:</strong> AWS manages encryption keys automatically.</p>
</li>
<li><p><strong>SSE-KMS:</strong> Uses AWS Key Management Service (KMS) for additional control over encryption keys.</p>
</li>
<li><p><strong>SSE-C:</strong> Customers provide their own encryption keys.</p>
</li>
</ul>
</li>
<li><p><strong>Client-Side Encryption:</strong></p>
<ul>
<li>Data is encrypted <strong>before</strong> being uploaded to S3 using customer-managed encryption keys.</li>
</ul>
</li>
</ol>
<p>We could spend endless time researching and exploring the theory behind Amazon S3, but practical application solidifies learning. Now, let’s move on to uploading a static website to Amazon S3.</p>
<p>As mentioned earlier, you should have a basic understanding of how AWS works, including signing up, signing in, and creating IAM users. This is crucial because we will use an IAM user to perform operations securely. Understanding Identity and Access Management is essential in AWS, as it ensures proper access control and security while managing resources.</p>
<h2 id="heading-how-to-upload-a-static-website-to-amazon-s3">How to Upload a Static Website to Amazon S3</h2>
<p>To upload a static website, you first need a static site. If you do not have one, you can use a free template from <a target="_blank" href="https://www.free-css.com/free-css-templates">Free CSS</a>.</p>
<p>I’ve also provided a ready-to-use template that you can clone from this GitHub repository: <a target="_blank" href="https://github.com/Oghenekparobo/Mediplus-free-template">Mediplus Free Template</a>.</p>
<p>Now that your static project is ready, let’s go to AWS and upload it to an Amazon S3 bucket.</p>
<p>Login to your AWS account using your IAM user credentials. Once logged in, you will be redirected to the AWS Management Console.</p>
<p>Your AWS Dashboard should look similar to this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742473569107/436e229b-f16d-4572-b26c-b2359d2ef738.jpeg" alt="AWS User or IAM user dashboard" class="image--center mx-auto" width="1902" height="926" loading="lazy"></p>
<p>Navigate to the S3 service by clicking on the S3 link in the AWS dashboard. If you don’t see it, which is unlikely, use the search bar at the top of the dashboard. Simply type "S3", and it will appear in the results. Click on it to proceed.</p>
<p>Once you arrive at the Amazon S3 page, you will see the Create Bucket button.</p>
<p>S3 buckets are the backbone for many applications, including content delivery, data backup, archiving, static website hosting, and big data storage for analytics.</p>
<h2 id="heading-what-is-a-bucket">What is a Bucket?</h2>
<p>Amazon S3 buckets are the fundamental storage containers within AWS Simple Storage Service that provide secure, scalable repositories for digital assets.</p>
<p>Each bucket features a globally unique name, regional deployment, and flat object storage architecture identified by unique keys.</p>
<p>With 99.999999999% durability through built-in redundancy, S3 buckets support crucial infrastructure needs including content distribution, data archiving, and static website hosting. Admins can implement comprehensive data governance through configurable access controls (policies, ACLs, IAM), lifecycle management, versioning capabilities, and encryption protocols to meet organizational security requirements.</p>
<h3 id="heading-how-to-create-a-bucket">How to Create a Bucket</h3>
<p>To create a bucket, click on the "Create bucket" button. You will then be redirected to the bucket creation page.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742476384065/3a7df14d-2c5c-4169-98e9-473e7d96590b.jpeg" alt="create bucket page" class="image--center mx-auto" width="1911" height="891" loading="lazy"></p>
<p>Choose any name you like, as long as it follows AWS bucket naming rules.</p>
<p>You will also see various configuration options, but for now, leave them as default. We will make the necessary adjustments later in the project.</p>
<p>Finally, click on "Create bucket", and just like that your bucket is created! You should now see your bucket, which acts as a container for storing your files or data:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742477392285/409692a6-7838-42d4-bc61-e691b0d41477.jpeg" alt="409692a6-7838-42d4-bc61-e691b0d41477" class="image--center mx-auto" width="1917" height="886" loading="lazy"></p>
<h3 id="heading-how-to-upload-files-and-folders-to-an-s3-bucket">How to Upload Files and Folders to an S3 Bucket</h3>
<p>Now, let’s upload the static site we created or <a target="_blank" href="https://github.com/Oghenekparobo/Mediplus-free-template">cloned</a> to our S3 bucket.</p>
<p><strong>1. Click on your bucket:</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742479043138/19d5ee6b-67b2-422e-a1d5-e021d0248ef3.jpeg" alt="highlighting the bucket to click" class="image--center mx-auto" width="1918" height="881" loading="lazy"></p>
<p>After clicking on your bucket, you will be taken to the bucket details page, where you can manage various settings and configurations. This page provides options to adjust permissions and properties, monitor metrics, manage access points, and upload files using the "Upload" button.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742479342643/deec0a64-6c4b-4670-8c38-e9949c18a93e.jpeg" alt="created bucket page or bucket  info page" class="image--center mx-auto" width="1907" height="887" loading="lazy"></p>
<p><strong>2. Upload project files:</strong></p>
<p>If your static site project is ready, it should contain the essential files needed for deployment. While the structure may vary, it should include an index.html file, along with necessary assets such as images (or an image folder), CSS files (or a CSS folder), and JavaScript files (or a JS folder) to ensure proper functionality and styling.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742479661115/367b7346-3762-4405-a612-fd5cb0d59094.jpeg" alt="project or static site files" class="image--center mx-auto" width="1916" height="1078" loading="lazy"></p>
<p>Let’s start by uploading the necessary files according to our project structure. Start with the root-level files such as index.html, along with any other essential files on the root-level. Ensure you follow your project structure carefully and upload all required files first to maintain proper structure.</p>
<p>Then click on the upload button:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742479955714/306058d9-1629-4185-b480-d9e1ca4a6e91.jpeg" alt="upload page" class="image--center mx-auto" width="1903" height="913" loading="lazy"></p>
<p>On the Upload page, you will see the "Add files" and "Add folder" buttons. Let's start by uploading individual files. Click on "Add files" to begin selecting and uploading the necessary files.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742480188360/b4a5d67f-14ed-4675-b846-8e71cf9080d5.jpeg" alt="adding files" class="image--center mx-auto" width="1934" height="998" loading="lazy"></p>
<p>After successfully uploading your files, ensure you follow your project structure. If your static site only requires individual files, proceed accordingly. But if your project relies on specific folders for assets like images and CSS, you’ll need to upload those as well. In my case, my project structure includes folders for images and CSS e.t.c, so I will be uploading both files and folders.</p>
<h4 id="heading-3-upload-project-folders">3. Upload project folders</h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742483020056/6454b351-b962-4e71-845b-b6c90a3bd2db.jpeg" alt="project folders" class="image--center mx-auto" width="298" height="607" loading="lazy"></p>
<p>To upload your folders click on the “Add folder” button.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742483175794/0f96e172-146a-4513-adda-4e7bfb5281ec.jpeg" alt="highlighing the Add folder button in the bucket page" class="image--center mx-auto" width="1887" height="897" loading="lazy"></p>
<p>Now, upload your folders by clicking on the "Add folder" button and selecting the necessary folders, such as those containing images, CSS, or JavaScript files, based on your project structure.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742483490417/254d6fc4-ca3c-427d-acb5-1ffab46ed15f.jpeg" alt="how successful folder upload looks like " class="image--center mx-auto" width="1917" height="896" loading="lazy"></p>
<p>You will notice that as you upload folders, S3 automatically extracts and structures the files by name, type, and size. This can sometimes be confusing for beginners, as the files are displayed in a structured format.</p>
<p>Once you have successfully uploaded your files and folders, scroll down and click the "Upload" button to complete the process.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742483726834/890c70db-e2e4-4b0f-aac3-29b215276f97.jpeg" alt="highlighting where the upload button is " class="image--center mx-auto" width="1898" height="887" loading="lazy"></p>
<p>After clicking on "Upload", AWS S3 will begin uploading your files. You will see a progress indicator showing the status of each file being uploaded in real time.</p>
<p>Upon completing the upload, you will see a confirmation message stating "Upload Succeeded." AWS S3 will then generate a URL which you can find in the Summary section of the bucket’s details page. (Refer to the image below for reference.)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742485405264/af04f296-e447-4747-b060-2f9b86d7a0e0.jpeg" alt="showcasing Upload Succeeded " class="image--center mx-auto" width="1932" height="926" loading="lazy"></p>
<p>Congratulations! you have successfully uploaded your project into AWS S3. Now let’s make this accessible on the web.</p>
<p>Click on the "Close" button to exit the upload page. Now, while still in your bucket, let's configure the necessary settings and permissions to ensure proper access and functionality for our static website.</p>
<h2 id="heading-how-to-set-permissions-and-properties-in-aws-s3"><strong>How to Set Permissions and Properties in AWS S3</strong></h2>
<p>After uploading your files, the next step is to configure permissions and properties to ensure your static website functions correctly.</p>
<ul>
<li><p><strong>Permissions</strong>: By default, S3 objects are private. To make your website publicly accessible, you need to adjust the bucket policy and object permissions accordingly.</p>
</li>
<li><p><strong>Properties</strong>: You can configure various settings such as versioning, encryption, and static website hosting under the Properties tab of your bucket, but we won’t be going too deep as we are just going through the basics.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742485786885/3290bf47-6d32-4956-957c-527ba30357ed.jpeg" alt="bucket page with files" class="image--center mx-auto" width="1898" height="927" loading="lazy"></p>
<p>Let’s start with setting up permissions:</p>
<h3 id="heading-permissions">Permissions</h3>
<p>By default, public access is blocked in S3 for security reasons. But for this tutorial, we will be enabling public access to ensure our static website is accessible to users.</p>
<p>In the image below, you can see that the public access is blocked by default:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742486056880/5e5ae64d-ee88-4f7e-af4e-989f40850d21.jpeg" alt="blocked public access" class="image--center mx-auto" width="1962" height="891" loading="lazy"></p>
<p>To enable public access, click on the "Edit" button in the top-right corner. Then, uncheck the "Block all public access" option. After that, click on "Save changes" to apply the update.</p>
<p>Now you can see that the public access has been blocked:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742486660352/50508b0d-0d51-4f48-8bf0-d2a6f01fc5c8.jpeg" alt="Blocked public access" class="image--center mx-auto" width="1917" height="917" loading="lazy"></p>
<p>Public access is now enabled. Click on "Save changes" to confirm and apply the update.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742486981885/6a9d687f-8fef-40a8-b585-de9c23126aa2.jpeg" alt="Public access now enabled, click on save changes to validate the settings" class="image--center mx-auto" width="1946" height="923" loading="lazy"></p>
<h4 id="heading-enabling-public-access">Enabling public access</h4>
<p>AWS S3 will prompt you to enter a confirmation text to validate the settings. Input the required text and confirm, and public access will be enabled successfully.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742487256155/d3efe5d2-e42f-46d6-8052-4d6cb9a99ce8.jpeg" alt="confirmation prompt" class="image--center mx-auto" width="1917" height="920" loading="lazy"></p>
<p>So now that we have enabled public access, let’s set a bucket policy.</p>
<h3 id="heading-what-is-a-bucket-policy"><strong>What is a Bucket Policy?</strong></h3>
<p>A bucket policy is a JSON-based access control policy that defines permissions for your S3 bucket. It allows you to specify who can access your bucket and what actions they can perform.</p>
<p>With a bucket policy, you can:</p>
<ul>
<li><p>Grant or restrict public access to objects in your bucket.</p>
</li>
<li><p>Allow specific AWS users or services to interact with your bucket.</p>
</li>
<li><p>Define read, write, or delete permissions for different users.</p>
</li>
</ul>
<p>In this next section, we will configure a bucket policy to make our static website publicly accessible.</p>
<h2 id="heading-how-to-set-up-a-bucket-policy"><strong>How to Set Up a Bucket Policy</strong></h2>
<p>Setting up a bucket policy is simple. Click on the "Edit" button in the Bucket Policy section and paste the following JSON policy into the editor:</p>
<pre><code class="lang-markdown">{
  "Version": "2012-10-17",
  "Statement": [
<span class="hljs-code">    {
      "Sid": "PublicReadGetObject",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::your-bucket-name/*"
    }
  ]
}</span>
</code></pre>
<h4 id="heading-understanding-the-policy-attributes">Understanding the policy attributes:</h4>
<ul>
<li><p><strong>Version</strong>: Defines the policy language version. The <code>"2012-10-17"</code> version is the latest and most commonly used for S3 policies.</p>
</li>
<li><p><strong>Statement</strong>: A list of rules that define what actions are allowed or denied.</p>
</li>
<li><p><strong>Sid (Statement ID)</strong>: A unique identifier for the policy statement (optional but useful for reference).</p>
</li>
<li><p><strong>Effect:</strong> Specifies whether the rule allows or denies the specified action. In this case, it’s <code>"Allow"</code>.</p>
</li>
<li><p><strong>Principal</strong>: Defines who has access. The <code>"*"</code> means anyone (public access).</p>
</li>
<li><p><strong>Action</strong>: Specifies the allowed operation. <code>"s3:GetObject"</code> allows users to retrieve (read) objects from the bucket.</p>
</li>
<li><p><strong>Resource</strong>: Defines the specific bucket and objects the policy applies to. Replace <code>"your-bucket-name"</code> with your actual bucket name. The <code>"/*"</code> means all objects within the bucket.</p>
</li>
</ul>
<p>This policy makes all objects in the bucket publicly readable, allowing users to access your static website files via a browser.</p>
<p>if you followed closely, your bucket policy page should look like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742489775324/07439366-6f64-401b-a48a-1ce20536df5c.jpeg" alt="07439366-6f64-401b-a48a-1ce20536df5c" class="image--center mx-auto" width="1917" height="892" loading="lazy"></p>
<p>Now, click on the "Save changes" button, usually located at the bottom right of the page, to apply your changes.</p>
<p>After following the required steps Bucket policy has been set and public access has been enabled.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742490328543/58488d6e-1bc0-4acc-86da-e137e8fc5180.jpeg" alt=" Bucket policy has been set and public access has been enabled." class="image--center mx-auto" width="1895" height="886" loading="lazy"></p>
<p>Congratulations! You have successfully set up your bucket policy and enabled public access, meaning anyone can now access your website. But wait, where's the URL?</p>
<p>There's one last step: we need to enable static website hosting. To do this, you’ll need to navigate to the Properties tab and configure static website hosting so your site can be accessed on the web. I’ll walk you through the process in the next section.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742490657866/e9bb25a9-7fc2-478b-a531-2da041d4b740.jpeg" alt="permissions tab" class="image--center mx-auto" width="1886" height="876" loading="lazy"></p>
<h2 id="heading-how-to-enable-static-hosting">How to Enable Static Hosting</h2>
<p>Enabling static website hosting is simple. Scroll to the bottom of the Properties tab, and you will find the Static website hosting section. By default, this option is disabled. Let's enable it to make our website accessible on the web.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742490851582/ffa1b0b1-82f4-40d4-951d-bc494681620e.jpeg" alt="static web hosting section in the permissions tab" class="image--center mx-auto" width="1900" height="892" loading="lazy"></p>
<p>Click on the "Edit" button on the right side of the Static website hosting section. By default, this option is disabled, so change it to Enabled. Once enabled, you will see several configuration options, most of which are optional for this tutorial.</p>
<p>The most important setting here is the Index document, which specifies the default file that loads when someone accesses your site. The placeholder text indicates that it expects an <code>index.html</code> file. Simply type <code>index.html</code> in the Index document field to proceed.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742491006060/91ba5d52-acb4-42c8-802d-7cfd9a24e8c8.jpeg" alt="setting up static hosting" class="image--center mx-auto" width="1882" height="892" loading="lazy"></p>
<p>After entering <code>index.html</code> in the Index document field, scroll down and click on "Save changes" to apply the configuration.</p>
<p>After successfully applying the changes, you should find your Static Website URL at the bottom of the Static website hosting section in the Properties tab of your bucket.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742491854680/8393bbf2-27e6-4d21-b41f-d48e8e03fb09.jpeg" alt="static website url " class="image--center mx-auto" width="1973" height="906" loading="lazy"></p>
<p>Congratulations! You have just hosted your website on AWS S3. That’s a solid step into the world of DevOps.</p>
<p>Your URL should look something like this: <a target="_blank" href="http://your-bucket-name.s3-website.your-region.amazonaws.com/"><code>http://your-bucket-name.s3-website.your-region.amazonaws.com/</code></a>.</p>
<p>Simply copy and paste your Static Website URL into your browser to see your hosted site live.</p>
<p>Now, you have the skills to host a static website for a client and share the URL with them.</p>
<p>So go grab some coffee and banana bread, you have earned it. Then we’ll move on to the next part of the tutorial.</p>
<h2 id="heading-amazon-cloudfront">Amazon CloudFront</h2>
<p>Amazon CloudFront is a fast, highly secure, and programmable content delivery network (CDN) that improves website performance and security.</p>
<p>Our static website is hosted on S3(<a target="_blank" href="http://your-bucket-name.s3-website.your-region.amazonaws.com/"><code>http://your-bucket-name.s3-website.your-region.amazonaws.com/</code></a>) – but you might notice that the site is accessible via HTTP but lacks proper security measures such as SSL/TLS encryption. CloudFront addresses these limitations by providing a secure and scalable way to serve both static and dynamic content globally.</p>
<p>It delivers content to users with low latency by caching copies of your website at edge locations worldwide. When a user requests a resource, CloudFront serves it from the nearest edge location, significantly improving speed and availability.</p>
<h3 id="heading-key-features-of-amazon-cloudfront">Key Features of Amazon CloudFront</h3>
<ol>
<li><p><strong>Secure content delivery</strong>: CloudFront supports SSL/TLS encryption, ensuring data is transferred securely between clients and servers.</p>
</li>
<li><p><strong>DDoS protection</strong>: Integrated with AWS Shield, CloudFront helps mitigate Distributed Denial of Service (DDoS) attacks.</p>
</li>
<li><p><strong>Global content caching</strong>: CloudFront caches content at multiple edge locations, reducing server load and latency.</p>
</li>
<li><p><strong>Customizable distribution</strong>: Users can configure cache behavior, origin settings, and security policies.</p>
</li>
<li><p><strong>Seamless integration with AWS tools</strong>: CloudFront integrates with AWS Lambda@Edge, S3, EC2, and API Gateway, supporting both static and dynamic content delivery.</p>
</li>
<li><p><strong>Cost optimization</strong>: Reduces data transfer costs by caching and serving content from edge locations instead of directly from the origin.</p>
</li>
</ol>
<h3 id="heading-why-amazon-s3-alone-is-not-enough">Why Amazon S3 Alone is Not Enough</h3>
<p>Amazon S3 is a scalable and durable object storage service, but it lacks key features required for securely serving web content.</p>
<p>First of all, it doesn’t have HTTPS by default. When hosting a site on S3, it is only accessible over HTTP unless additional configurations are made.</p>
<p>Second, it has higher latency. S3 buckets are hosted in a specific AWS region, which may result in slower content delivery for users in different locations.</p>
<p>It also doesn’t have built-in caching, which means that every request is served from S3, increasing response time and potential costs.</p>
<p>And finally, there’s no DDoS protection – unlike CloudFront, S3 does not provide native protection against cyberattacks.</p>
<h3 id="heading-why-you-should-serve-your-website-with-cloudfront">Why You Should Serve Your Website with CloudFront</h3>
<p>While Amazon S3 is a great storage solution, it lacks the security and performance optimizations needed for web hosting. Amazon CloudFront enhances security with SSL/TLS encryption, improves performance with global caching, and provides robust security features like DDoS protection.</p>
<p>By leveraging CloudFront, you can ensure your website is not only fast but also secure, scalable, and cost-effective.</p>
<p>To get started with hosting your site on CloudFront, navigate to the CloudFront service page in AWS. You can do this by going to your AWS Management Console homepage or dashboard and searching for “CloudFront” using the search bar in the top left corner. Click on “CloudFront” from the search results to proceed.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742514549138/97c31675-81dc-4d6f-8f2c-28867bbb7ee2.jpeg" alt="cloud front can be navigated on the dashboard" class="image--center mx-auto" width="1891" height="888" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742514582663/14f0b8b4-3556-4ed2-a309-643d5fcee263.jpeg" alt="you can search for CloudFront on the search bar" class="image--center mx-auto" width="1891" height="865" loading="lazy"></p>
<p>Once you navigate to the AWS CloudFront page, you will see a Create Distribution button. Click on it to begin setting up CloudFront for your website.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742515301740/ffe72934-b250-4adb-8cf7-e9cafd5c4ac2.jpeg" alt="ffe72934-b250-4adb-8cf7-e9cafd5c4ac2" class="image--center mx-auto" width="1941" height="940" loading="lazy"></p>
<h3 id="heading-what-is-a-cloudfront-distribution"><strong>What is a CloudFront Distribution?</strong></h3>
<p>A CloudFront distribution is the configuration that defines how CloudFront delivers content to users. It acts as a link between your origin server (such as an S3 bucket) and CloudFront’s global network of edge locations. When a user requests your site, CloudFront retrieves content from the nearest edge location instead of always fetching it from the origin, ensuring faster load times, reduced latency, and improved security.</p>
<p>There are two types of distributions in CloudFront:</p>
<ol>
<li><p><strong>Web Distribution</strong>: Used for websites, APIs, and dynamic or static content.</p>
</li>
<li><p><strong>RTMP Distribution</strong> (Deprecated): Previously used for streaming media (now replaced by modern streaming services).</p>
</li>
</ol>
<p>For our S3-hosted website, we will be creating a Web Distribution to securely serve content over HTTPS while improving speed and reliability.</p>
<p>After clicking the Create Distribution button, you will be directed to the Create Distribution page, where you can configure various settings. In this tutorial, we will focus on the essential options.</p>
<ol>
<li><p>Under the Origin section, select your S3 bucket as the origin domain.</p>
</li>
<li><p>If your S3 bucket has static website hosting enabled, AWS recommends using the S3 website endpoint instead of the default bucket endpoint.</p>
</li>
<li><p>Enter the correct S3 website endpoint in the Origin domain field. For example:</p>
<pre><code class="lang-markdown"> freecodecampbuckettutorial.s3-website.ca-central-1.amazonaws.com
</code></pre>
</li>
<li><p>Ensure you use the S3 website endpoint rather than the standard S3 bucket URL to avoid issues with accessing your site.</p>
</li>
</ol>
<p>By following these steps, you ensure that CloudFront correctly serves your static website from the S3 bucket.</p>
<p>Make sure that you use the S3 website endpoint rather than the standard S3 bucket URL to avoid issues with accessing your site. This will be suggested to you descriptively while filling in the origin name. Click on “Use website endpoint” and it will populate the field with the S3 bucket website endpoint instead of the bucket URL.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742516012771/9c631915-1a1e-415d-a614-5aab854db6ed.jpeg" alt="select distribution" class="image--center mx-auto" width="1883" height="877" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742515900001/01d173b4-8f2d-4289-96e1-e688ebafa34e.jpeg" alt="select s3 bucket on origin domain" class="image--center mx-auto" width="1888" height="855" loading="lazy"></p>
<p>Next, scroll down to the Web Application Firewall (WAF) section at the bottom of the page and enable security protections to safeguard your website from common web threats. Select “Create Distribution” to deploy your CloudFront distribution.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742516241133/c587b52a-9734-40bb-80cc-a882b9bbbd44.jpeg" alt="enable waf" class="image--center mx-auto" width="1948" height="885" loading="lazy"></p>
<p>Your CloudFront Distributions page should now display the newly created distribution. The page will include key details such as:</p>
<ul>
<li><p><strong>Distribution ID</strong>: A unique identifier for your CloudFront distribution.</p>
</li>
<li><p><strong>Domain Name</strong>: The CloudFront-provided URL (for example, <a target="_blank" href="http://d1234abcd.cloudfront.net"><code>d1234abcd.cloudfront.net</code></a>), which you can use to access your site.</p>
</li>
</ul>
<p>After creating your CloudFront distribution, navigate to your Distributions page and check the Last Modified section for its status. If the status shows Deploying, you will need to wait until it changes, which may take several minutes.</p>
<p>Once the deployment is complete, the status will typically update to a timestamp, indicating that the distribution is ready for use. Ensure the status has changed before proceeding with further configurations or accessing your CloudFront distribution.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742525346316/cd9d88a0-7c03-46d8-a794-f3ddd64ffdda.jpeg" alt="last modified page" class="image--center mx-auto" width="1873" height="921" loading="lazy"></p>
<p>Your website is now successfully hosted on CloudFront!</p>
<p>We now have our CloudFront Domain Name (for example, <a target="_blank" href="http://d1234abcd.cloudfront.net"><code>d1234abcd.cloudfront.net</code></a>), which you can find in the Details section of your distribution. Before making any further changes, let’s preview the site by copying and pasting the domain name into a web browser.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742516439826/0bfd3393-8ec0-45cc-a643-0e1d31c013cf.jpeg" alt="content distribution created" class="image--center mx-auto" width="1877" height="987" loading="lazy"></p>
<p>At this stage, when you try to access your website using the CloudFront Domain Name, you’ll notice that the site cannot be reached. This happens because CloudFront does not yet have permission to fetch content from your S3 bucket.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742518594690/0808d92b-b8c5-45b3-a89c-f54452eec9a6.jpeg" alt="cloud front domain unreachable page" class="image--center mx-auto" width="1892" height="998" loading="lazy"></p>
<p>To fix this, you need to update your S3 bucket policy to explicitly allow CloudFront to access your objects. You can do this by adding a condition that grants access to requests coming specifically from your CloudFront distribution.</p>
<h3 id="heading-understanding-the-amazon-resource-name-arn"><strong>Understanding the Amazon Resource Name (ARN)</strong></h3>
<p>An Amazon Resource Name (ARN) is a unique identifier assigned to AWS resources. Every CloudFront distribution has its own ARN, which you can find at the top of the CloudFront distribution details page. It looks like something like this:</p>
<pre><code class="lang-markdown">arn:aws:cloudfront::123456789012:distribution/E2ABC3XYZ456
</code></pre>
<p>This ARN is crucial because we use it in our bucket policy to restrict access to only our CloudFront distribution, ensuring no other services or users can retrieve data directly from our S3 bucket.</p>
<h3 id="heading-updated-s3-bucket-policy"><strong>Updated S3 Bucket Policy</strong></h3>
<p>To allow CloudFront to serve content from our S3 bucket, we update the S3 bucket policy as follows:</p>
<pre><code class="lang-markdown">{
  "Version": "2012-10-17",
  "Statement": [
<span class="hljs-code">    {
      "Effect": "Allow",
      "Principal": {
        "Service": "cloudfront.amazonaws.com"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::your-bucket-name/*",
      "Condition": {
        "StringEquals": {
          "AWS:SourceArn": "arn:aws:cloudfront::[ACCOUNT_ID]:distribution/[DISTRIBUTION_ID]"
        }
      }
    },
    {
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::your-bucket-name/*"
    }
  ]
}</span>
</code></pre>
<p>Breaking down the bucket policy:</p>
<ol>
<li><p><strong>First statement that grants CloudFront access:</strong></p>
<ul>
<li><p><code>“Effect”: “Allow”</code>: This permits access to the specified resource.</p>
</li>
<li><p><code>"Principal": { "Service": "</code><a target="_blank" href="http://cloudfront.amazonaws.com"><code>cloudfront.amazonaws.com</code></a><code>" }</code>: Grants access specifically to CloudFront.</p>
</li>
<li><p><code>"Action": "s3:GetObject"</code>: Allows CloudFront to retrieve objects from the S3 bucket.</p>
</li>
<li><p><code>"Resource": "arn:aws:s3:::your-bucket-name/*"</code>: Grants access to all objects in the S3 bucket.</p>
</li>
<li><p><code>“Condition”</code>: Ensures that only requests originating from our CloudFront distribution are allowed using:</p>
<pre><code class="lang-markdown">  AWS:SourceArn": "arn:aws:cloudfront::123456789012:distribution/E2ABC3XYZ456
</code></pre>
</li>
</ul>
</li>
<li><p><strong>Second statement (optional, and grants public access)</strong></p>
<ul>
<li><p>This statement allows all users (<code>Principal: "*"</code>) to access the S3 objects.</p>
</li>
<li><p>If you want to restrict access only to CloudFront, you can remove this second statement.</p>
</li>
</ul>
</li>
</ol>
<p>After editing and updating your S3 bucket policy to allow CloudFront access, you can refresh the page where you preview your CloudFront domain name (for example, <a target="_blank" href="http://d1234abcd.cloudfront.net"><code>d1234abcd.cloudfront.net</code></a>). Open it in a browser, and if you have carefully followed all the instructions, you have successfully hosted a static site on S3 and CloudFront.</p>
<p>Your website is now fully protected, well done! Congratulations, DevOps pro!</p>
<p><strong>FIST BUMPS!</strong></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this guide, we’ve only scratched the surface of what AWS can do. S3 and CloudFront are powerful services, but there’s still so much more you can explore, from advanced security settings to automation and performance optimizations.</p>
<p>As you continue your AWS journey, you can dive deeper into topics like caching strategies, custom domain configurations, and integrating AWS Lambda@Edge for dynamic content. The possibilities are endless.</p>
<p>This is just the beginning, and you’re off to a great start. Keep experimenting, keep learning, and soon we will be mastering even more advanced AWS capabilities. Happy building! 🚀</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ ​​How to Upload Large Files Efficiently with AWS S3 Multipart Upload ]]>
                </title>
                <description>
                    <![CDATA[ Imagine running a media streaming platform where users upload large high-definition videos. Uploading such large files can be slow and may fail if the network is unreliable.  Using traditional single-part uploads can be cumbersome and inefficient for... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/upload-large-files-with-aws/</link>
                <guid isPermaLink="false">66b906c2cacc627a9522d23c</guid>
                
                    <category>
                        <![CDATA[ AWS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ S3 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Destiny Erhabor ]]>
                </dc:creator>
                <pubDate>Mon, 08 Jul 2024 12:02:56 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/07/mr-cup-fabien-barral-o6GEPQXnqMY-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Imagine running a media streaming platform where users upload large high-definition videos. Uploading such large files can be slow and may fail if the network is unreliable. </p>
<p>Using traditional single-part uploads can be cumbersome and inefficient for large files, often leading to timeout errors or the need to restart the entire upload process if any part fails. This is where the Amazon S3 multipart upload feature comes into play, offering a robust solution to these challenges.</p>
<p>In this article, you'll explore how to efficiently handle large files with Amazon S3 multipart upload. We'll discuss the benefits of using this feature, walk through the process of uploading files in parts, and provide code examples using the AWS SDK for full-stack Node and React project. </p>
<p>By the end of this article, you should have a good understanding of how to leverage the Amazon S3 multipart upload to optimize file uploads in your applications.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before we start, ensure you have the following:</p>
<ul>
<li>An AWS account with IAM user credentials.</li>
<li>Node.js installed on your development machine.</li>
<li>Basic knowledge of JavaScript, React, and Node.js.</li>
</ul>
<h2 id="heading-table-of-contents">Table of Contents:</h2>
<ul>
<li><a class="post-section-overview" href="#">Introduction</a></li>
<li><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></li>
<li><a class="post-section-overview" href="#table-of-contents">Table of Contents</a></li>
<li><a class="post-section-overview" href="#heading-how-it-works">How it works</a></li>
<li><a class="post-section-overview" href="#heading-step-1-how-to-set-up-aws-s3">Step 1: How to Set Up AWS S3</a></li>
<li><a class="post-section-overview" href="#heading-how-to-create-an-s3-bucket">How to Create an S3 Bucket</a></li>
<li><a class="post-section-overview" href="#heading-how-to-configure-s3-bucket-policy">How to Configure s3 Bucket Policy</a></li>
<li><a class="post-section-overview" href="#step-2-how-to-set-up-aws-s3-backend-with-nodejs">Step 2: How to Set Up AWS S3 Backend with Node.js</a></li>
<li><a class="post-section-overview" href="#initialize-a-nodejs-project">How to Initialize a Node.js Project</a></li>
<li><a class="post-section-overview" href="#heading-install-required-packages-1">Install Required Packages</a></li>
<li><a class="post-section-overview" href="#heading-create-server-file">Create Server file</a></li>
<li><a class="post-section-overview" href="#imports-and-configuration">Imports and configuration</a></li>
<li><a class="post-section-overview" href="#heading-middleware-and-aws-configuration">Middleware and AWS Configuration</a></li>
<li><a class="post-section-overview" href="#heading-routes">Routes</a></li>
<li><a target="_blank" href="https://www.freecodecamp.org/news/p/d96e9e12-b460-4784-b0cf-88855383af4d/start-initialize-upload-endpoint">Start/Initialize Upload Endpoint</a></li>
<li><a class="post-section-overview" href="#heading-upload-part-endpoint">Upload Part Endpoint</a></li>
<li><a class="post-section-overview" href="#heading-complete-upload-endpoint">Complete Upload Endpoint</a></li>
<li><a class="post-section-overview" href="#heading-start-the-server">Start the Server</a></li>
<li><a class="post-section-overview" href="#heading-environment-variables">Environment Variables</a></li>
<li><a class="post-section-overview" href="#heading-running-the-server">Running the Server</a></li>
<li><a class="post-section-overview" href="#heading-step-3-how-to-set-up-the-frontend-with-react">Step 3: How to Set Up the Frontend with React</a></li>
<li><a class="post-section-overview" href="#initialize-a-react-project">How to Initialize a React Project</a></li>
<li><a class="post-section-overview" href="#heading-install-required-packages-1">Install Required Packages</a></li>
<li><a class="post-section-overview" href="#heading-create-components">Create Components</a></li>
<li><a class="post-section-overview" href="#heading-app-component">App Component</a></li>
<li><a class="post-section-overview" href="#heading-testing">Testing</a></li>
<li><a class="post-section-overview" href="#heading-part-upload">Part Upload</a></li>
<li><a class="post-section-overview" href="#heading-complete-part-upload">Complete Part Upload</a></li>
<li><a class="post-section-overview" href="#heading-full-code-on-github">Full Code on GitHub</a></li>
<li><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></li>
</ul>
<h2 id="heading-how-it-works">How It Works</h2>
<p>A large file upload is divided into smaller parts/chunks, each part is uploaded independently to Amazon S3. Once all the parts have been uploaded, they are combined to create the final object.</p>
<p>Example: Uploading a 100MB file in 5MB parts would result in 20 parts being uploaded to S3. Each part is uploaded with a unique identifier, and the order is maintained to ensure that the file can be reassembled correctly.</p>
<p>Retries can be configured to automatically retry failed parts, and the upload can be paused and resumed at any time. This makes the process more robust and fault-tolerant, especially for large files.</p>
<p><img src="https://media.amazonwebservices.com/blog/s3_multipart_upload.png" alt="https://media.amazonwebservices.com/blog/s3_multipart_upload.png" width="341" height="377" loading="lazy">
<em>multipart AWS s3 uploads</em></p>
<p>Learn more on the <a target="_blank" href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/mpuoverview.html">Amazon S3 multipart upload docs</a>.</p>
<p>Let's get started!</p>
<h2 id="heading-step-1-how-to-set-up-aws-s3">Step 1: How to Set Up AWS S3</h2>
<h3 id="heading-how-to-create-an-s3-bucket">How to Create an S3 Bucket</h3>
<p>First, log into the AWS Management console</p>
<ul>
<li>Navigate to the S3 service.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/06/create-bucket.png" alt="How to create an s3 bucket" width="600" height="400" loading="lazy">
<em>How to create an s3 bucket</em></p>
<p>Create a new bucket and take note of the bucket name.</p>
<p>Uncheck the Public Access settings for simplicity We'll also configure bucket access using IAM policies after creating the bucket.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/06/create-bucket2.png" alt="How to create an s3 bucket" width="600" height="400" loading="lazy">
<em>How to create an s3 bucket</em></p>
<ul>
<li>Leave other settings as default and create the bucket.</li>
</ul>
<h3 id="heading-how-to-configure-s3-bucket-policy">How to Configure S3 Bucket Policy</h3>
<p>Now, that you have created the bucket, let's set up the policy to allow users read your objects(file/videos) url.</p>
<ul>
<li>Click on the bucket name and navigate to the <code>Permissions</code> tab.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/06/permission.png" alt="How to configure s3 bucket policy" width="600" height="400" loading="lazy">
<em>How to configure s3 bucket policy</em></p>
<p>Navigate to the <code>Bucket Policy</code> section and click on Edit.</p>
<p>Input the following policy, and replace <code>your-bucket-name</code> with your actual bucket name:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"Version"</span>: <span class="hljs-string">"2012-10-17"</span>,
  <span class="hljs-attr">"Statement"</span>: [
    {
      <span class="hljs-attr">"Effect"</span>: <span class="hljs-string">"Allow"</span>,
      <span class="hljs-attr">"Principal"</span>: <span class="hljs-string">"*"</span>,
      <span class="hljs-attr">"Action"</span>: <span class="hljs-string">"s3:GetObject"</span>,
      <span class="hljs-attr">"Resource"</span>: <span class="hljs-string">"arn:aws:s3:::your-bucket-name/*"</span>
    }
  ]
}
</code></pre>
<p><code>Version</code>: Amazon S3 object version number for the bucket policy language.</p>
<p><code>Statement</code>: An array of one or more individual statements that define the policy.</p>
<p><code>Effect</code>: The effect determines whether the statement allows or denies access.</p>
<p><code>Principal</code>: The entity that the policy is applied to. In this case, we are allowing all principals. In production, you should specify the IAM user or role that needs access.</p>
<p><code>Action</code>: The action that the policy allows or denies. In this case, we are allowing the <code>s3:GetObject</code> action, which allows users to retrieve objects from the bucket.</p>
<p><code>Resource</code>: The Amazon Resource Name (ARN) of the bucket and objects that the policy applies to. In this case, we are allowing access to all objects in the bucket.</p>
<p>Click on Save changes to apply the policy.</p>
<h2 id="heading-step-2-how-to-set-up-aws-s3-backend-with-nodejs">Step 2: How to Set Up AWS S3 Backend with Node.js</h2>
<p>Next, let's set up the backend server with AWS SDK to handle the file upload process.</p>
<h3 id="heading-how-to-initialize-a-nodejs-project">How to Initialize a Node.js Project</h3>
<p>Create a new directory for your project and initialize a new Node.js project:</p>
<pre><code class="lang-bash">mkdir s3-multipart-upload
<span class="hljs-built_in">cd</span> s3-multipart-upload
npm init -y
</code></pre>
<h3 id="heading-install-required-packages">Install Required Packages</h3>
<p>Install the following packages using npm:</p>
<pre><code class="lang-bash"> npm install express dotenv multer aws-sdk
</code></pre>
<h3 id="heading-create-server-file">Create Server File</h3>
<p>Create a new file named <code>app.js</code> (For simplicity, we are going to use this file only for all the upload logic) and add the following code:</p>
<h4 id="heading-imports-and-configurations">Imports and Configurations</h4>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> cors = <span class="hljs-built_in">require</span>(<span class="hljs-string">"cors"</span>);
<span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">"express"</span>);
<span class="hljs-keyword">const</span> AWS = <span class="hljs-built_in">require</span>(<span class="hljs-string">"aws-sdk"</span>);
<span class="hljs-keyword">const</span> dotenv = <span class="hljs-built_in">require</span>(<span class="hljs-string">"dotenv"</span>);
<span class="hljs-keyword">const</span> multer = <span class="hljs-built_in">require</span>(<span class="hljs-string">"multer"</span>);

<span class="hljs-keyword">const</span> multerUpload = multer();
dotenv.config();

<span class="hljs-keyword">const</span> app = express();
<span class="hljs-keyword">const</span> port = <span class="hljs-number">3001</span>;
</code></pre>
<h5 id="heading-imports">Imports</h5>
<p><code>cors</code>: Middleware for enabling Cross-Origin Resource Sharing (CORS). This is necessary to allow your frontend application interact with the backend hosted on a different domain or port.</p>
<p><code>express</code>: A minimal and flexible Node.js web application framework.</p>
<p><code>AWS</code>: The AWS SDK for JavaScript, which allows you to interact with AWS services.</p>
<p><code>dotenv</code>: A module that loads environment variables from a <strong>.env</strong> file into <strong>process.env</strong>.</p>
<p><code>multer</code>: Middleware for handling multipart/form-data, which is primarily used for uploading files.</p>
<h5 id="heading-configurations">Configurations</h5>
<p><code>multerUpload</code>: Initializes <code>multer</code> for handling file uploads.</p>
<p><code>dotenv.config()</code>: Loads the environment variables from a .env file.</p>
<p><code>app</code>: Initializes an Express application.</p>
<p><code>port</code>: Sets the port on which the Express application will run.</p>
<h4 id="heading-middleware-and-aws-configuration">Middleware and AWS Configuration</h4>
<p>Next, add the following code to configure middleware and AWS SDK:</p>
<pre><code class="lang-javascript">app.use(cors());

AWS.config.update({
  <span class="hljs-attr">accessKeyId</span>: process.env.AWS_ACCESS_KEY,
  <span class="hljs-attr">secretAccessKey</span>: process.env.AWS_SECRET_KEY,
  <span class="hljs-attr">region</span>: process.env.AWS_REGION,
});

<span class="hljs-keyword">const</span> s3 = <span class="hljs-keyword">new</span> AWS.S3();
app.use(express.json({ <span class="hljs-attr">limit</span>: <span class="hljs-string">"50mb"</span> }));
app.use(express.urlencoded({ <span class="hljs-attr">limit</span>: <span class="hljs-string">"50mb"</span>, <span class="hljs-attr">extended</span>: <span class="hljs-literal">true</span> }));
</code></pre>
<p><code>app.use(cors())</code>: Enables CORS for all routes, allowing your frontend to communicate with the backend without issues related to cross-origin requests.</p>
<p><code>AWS.config.update({ ... })</code>: Configures the AWS SDK with the access key, secret key, and region from the environment variables.<br>const s3 = new AWS.S3(): Creates an instance of the S3 service.</p>
<p><code>app.use(express.json({ limit: '50mb' }))</code>: Configures Express to parse JSON bodies with a size limit of 50MB.</p>
<p><code>app.use(express.urlencoded({ limit: '50mb', extended: true }))</code>: Configures Express to parse URL-encoded bodies with a size limit of 50MB.</p>
<h3 id="heading-routes">Routes</h3>
<p>It's time to start creating our routes. The routes required for the multipart upload process are as follows:</p>
<ul>
<li>Initialization of the upload process.</li>
<li>Uploading parts of the file.</li>
<li>Completing the upload process.</li>
</ul>
<h4 id="heading-startinitialize-upload-endpoint">Start/Initialize Upload Endpoint</h4>
<p>This route puts the upload process in play. Add the following code to create an endpoint for initializing the multipart upload process:</p>
<pre><code class="lang-javascript">app.post(<span class="hljs-string">"/start-upload"</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">const</span> { fileName, fileType } = req.body;

  <span class="hljs-keyword">const</span> params = {
    <span class="hljs-attr">Bucket</span>: process.env.S3_BUCKET,
    <span class="hljs-attr">Key</span>: fileName,
    <span class="hljs-attr">ContentType</span>: fileType,
  };

  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> upload = <span class="hljs-keyword">await</span> s3.createMultipartUpload(params).promise();
    <span class="hljs-comment">// console.log({ upload });</span>
    res.send({ <span class="hljs-attr">uploadId</span>: upload.UploadId });
  } <span class="hljs-keyword">catch</span> (error) {
    res.send(error);
  }
});
</code></pre>
<p>The function above creates a POST endpoint <strong>/start-upload</strong> that expects a JSON body with <code>fileName</code> and <code>fileType</code> properties. It then uses the <code>createMultipartUpload</code> method from the S3 service to initialize the multipart upload process. If successful, it returns the <code>uploadId</code> to the user, which will be used to upload parts of the file.</p>
<h4 id="heading-upload-part-endpoint">Upload Part Endpoint</h4>
<p>This is the route where the different smaller parts of the large file upload are received and tagged. Add the following code to create an endpoint for uploading parts of the file:</p>
<pre><code class="lang-javascript">app.post(<span class="hljs-string">"/upload-part"</span>, multerUpload.single(<span class="hljs-string">"fileChunk"</span>), <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">const</span> { fileName, partNumber, uploadId, fileChunk } = req.body;

  <span class="hljs-keyword">const</span> params = {
    <span class="hljs-attr">Bucket</span>: process.env.S3_BUCKET,
    <span class="hljs-attr">Key</span>: fileName,
    <span class="hljs-attr">PartNumber</span>: partNumber,
    <span class="hljs-attr">UploadId</span>: uploadId,
    <span class="hljs-attr">Body</span>: Buffer.from(fileChunk, <span class="hljs-string">"base64"</span>),
  };

  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> uploadParts = <span class="hljs-keyword">await</span> s3.uploadPart(params).promise();
    <span class="hljs-built_in">console</span>.log({ uploadParts });
    res.send({ <span class="hljs-attr">ETag</span>: uploadParts.ETag });
  } <span class="hljs-keyword">catch</span> (error) {
    res.send(error);
  }
});
</code></pre>
<p>The function above creates a POST endpoint at <strong>/upload-part</strong> that expects a form-data body with <code>uploadId</code>, <code>partNumber</code>, and <code>fileName</code> properties. It uses the <code>uploadPart</code> method from the S3 service to upload the part of the file. If successful, it returns the <code>ETag</code> of the uploaded part to the client.</p>
<p>The <code>ETag</code> is a unique identifier for the upload part that will be used to complete the multipart upload.</p>
<h4 id="heading-complete-upload-endpoint">Complete Upload Endpoint</h4>
<p>Once the part has been uploaded, the final step is to combine all the parts to create the final object.</p>
<p>Add the following code to create an endpoint for completing the multipart upload process:</p>
<pre><code class="lang-js">app.post(<span class="hljs-string">"/complete-upload"</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">const</span> { fileName, uploadId, parts } = req.body;

  <span class="hljs-keyword">const</span> params = {
    <span class="hljs-attr">Bucket</span>: process.env.S3_BUCKET,
    <span class="hljs-attr">Key</span>: fileName,
    <span class="hljs-attr">UploadId</span>: uploadId,
    <span class="hljs-attr">MultipartUpload</span>: {
      <span class="hljs-attr">Parts</span>: parts,
    },
  };

  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> complete = <span class="hljs-keyword">await</span> s3.completeMultipartUpload(params).promise();
    <span class="hljs-built_in">console</span>.log({ complete });
    res.send({ <span class="hljs-attr">fileUrl</span>: complete.Location });
  } <span class="hljs-keyword">catch</span> (error) {
    res.send(error);
  }
});
</code></pre>
<p>The function above creates a POST endpoint at <strong>/complete-upload</strong> that expects a JSON body with <code>uploadId</code>, <code>fileName</code>, and <code>parts</code> properties. It uses the <code>completeMultipartUpload</code> method from the S3 service to combine the uploaded parts and creates the final object. If successful, it returns the data object containing <code>fileUrl</code> about the completed upload.</p>
<h3 id="heading-start-the-server">Start the Server</h3>
<p>Finally, add the following code to start the Express server:</p>
<pre><code class="lang-javascript">app.listen(port, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Server running on port <span class="hljs-subst">${port}</span>`</span>);
});
</code></pre>
<p>This code starts the Express server on port 3001 and logs a message to the console when the server is running.</p>
<h3 id="heading-environment-variables">Environment Variables</h3>
<p>Create a new file named .env in the root directory of your project and add the following environment variables:</p>
<pre><code class="lang-bash">AWS_ACCESS_KEY=your-access-key
AWS_SECRET_KEY=your-secret-key
AWS_REGION=your-region
S3_BUCKET=your-bucket-name
</code></pre>
<p>Replace <code>your-access-key</code>, <code>your-secret-key</code>, <code>your-region</code>, and <code>your-bucket-name</code> with your actual AWS credentials and bucket name.</p>
<h3 id="heading-running-the-server">Running the Server</h3>
<p>To run the server, execute the following command in your terminal:</p>
<pre><code class="lang-bash">node app.js
</code></pre>
<p>This will start the server on port 3001.</p>
<h2 id="heading-step-3-how-to-set-up-the-frontend-with-react">Step 3: How to Set Up the Frontend with React</h2>
<p>Now that the backend is set up, let's create a React frontend to interact with the server and upload files to S3 using the multipart upload process.</p>
<p>The frontend will be in charge of splitting the file into parts, uploading each part to the server, and completing the upload process.</p>
<h3 id="heading-how-to-initialize-a-react-project">How to Initialize a React Project</h3>
<p>Create a new React project using Create React App:</p>
<pre><code class="lang-bash">npx create-react-app s3-multipart-upload-frontend
<span class="hljs-built_in">cd</span> s3-multipart-upload-frontend
</code></pre>
<h3 id="heading-install-required-packages-1">Install Required Packages</h3>
<p>Install the following packages using npm:</p>
<pre><code class="lang-bash">  npm install axios
</code></pre>
<h3 id="heading-create-components">Create Components</h3>
<p>Create a new file named <strong>Upload.js</strong> in the src/components directory and add the following code:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React, { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> axios <span class="hljs-keyword">from</span> <span class="hljs-string">"axios"</span>;

<span class="hljs-keyword">const</span> CHUNK_SIZE = <span class="hljs-number">5</span> * <span class="hljs-number">1024</span> * <span class="hljs-number">1024</span>; <span class="hljs-comment">// 5MB</span>

<span class="hljs-keyword">const</span> FileUpload = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> [file, setFile] = useState(<span class="hljs-literal">null</span>);
  <span class="hljs-keyword">const</span> [fileUrl, setFileUrl] = useState(<span class="hljs-string">""</span>);

  <span class="hljs-keyword">const</span> handleFileChange = <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
    setFile(e.target.files[<span class="hljs-number">0</span>]);
  };

  <span class="hljs-keyword">const</span> handleFileUpload = <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> fileName = file.name;
    <span class="hljs-keyword">const</span> fileType = file.type;
    <span class="hljs-keyword">let</span> uploadId = <span class="hljs-string">""</span>;
    <span class="hljs-keyword">let</span> parts = [];

    <span class="hljs-keyword">try</span> {
      <span class="hljs-comment">// Start the multipart upload</span>
      <span class="hljs-keyword">const</span> startUploadResponse = <span class="hljs-keyword">await</span> axios.post(
        <span class="hljs-string">"http://localhost:3001/start-upload"</span>,
        {
          fileName,
          fileType,
        }
      );

      uploadId = startUploadResponse.data.uploadId;

      <span class="hljs-comment">// Split the file into chunks and upload each part</span>
      <span class="hljs-keyword">const</span> totalParts = <span class="hljs-built_in">Math</span>.ceil(file.size / CHUNK_SIZE);

      <span class="hljs-built_in">console</span>.log(totalParts);

      <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> partNumber = <span class="hljs-number">1</span>; partNumber &lt;= totalParts; partNumber++) {
        <span class="hljs-keyword">const</span> start = (partNumber - <span class="hljs-number">1</span>) * CHUNK_SIZE;
        <span class="hljs-keyword">const</span> end = <span class="hljs-built_in">Math</span>.min(start + CHUNK_SIZE, file.size);
        <span class="hljs-keyword">const</span> fileChunk = file.slice(start, end);

        <span class="hljs-keyword">const</span> reader = <span class="hljs-keyword">new</span> FileReader();
        reader.readAsArrayBuffer(fileChunk);

        <span class="hljs-keyword">const</span> uploadPart = <span class="hljs-function">() =&gt;</span> {
          <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =&gt;</span> {
            reader.onload = <span class="hljs-keyword">async</span> () =&gt; {
              <span class="hljs-keyword">const</span> fileChunkBase64 = btoa(
                <span class="hljs-keyword">new</span> <span class="hljs-built_in">Uint8Array</span>(reader.result).reduce(
                  <span class="hljs-function">(<span class="hljs-params">data, byte</span>) =&gt;</span> data + <span class="hljs-built_in">String</span>.fromCharCode(byte),
                  <span class="hljs-string">""</span>
                )
              );

              <span class="hljs-keyword">const</span> uploadPartResponse = <span class="hljs-keyword">await</span> axios.post(
                <span class="hljs-string">"http://localhost:3001/upload-part"</span>,
                {
                  fileName,
                  partNumber,
                  uploadId,
                  <span class="hljs-attr">fileChunk</span>: fileChunkBase64,
                }
              );

              parts.push({
                <span class="hljs-attr">ETag</span>: uploadPartResponse.data.ETag,
                <span class="hljs-attr">PartNumber</span>: partNumber,
              });
              resolve();
            };
            reader.onerror = reject;
          });
        };

        <span class="hljs-keyword">await</span> uploadPart();
      }

      <span class="hljs-comment">// Complete the multipart upload</span>
      <span class="hljs-keyword">const</span> completeUploadResponse = <span class="hljs-keyword">await</span> axios.post(
        <span class="hljs-string">"http://localhost:3001/complete-upload"</span>,
        {
          fileName,
          uploadId,
          parts,
        }
      );

      setFileUrl(completeUploadResponse.data.fileUrl);
      alert(<span class="hljs-string">"File uploaded successfully"</span>);
    } <span class="hljs-keyword">catch</span> (error) {
      <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Error uploading file:"</span>, error);
    }
  };

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"file"</span> <span class="hljs-attr">onChange</span>=<span class="hljs-string">{handleFileChange}</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">disabled</span>=<span class="hljs-string">{!file}</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{handleFileUpload}</span>&gt;</span>
        Upload
      <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">hr</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">br</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">br</span> /&gt;</span>
      {fileUrl &amp;&amp; (
        <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">{fileUrl}</span> <span class="hljs-attr">target</span>=<span class="hljs-string">"_blank"</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"noopener noreferrer"</span>&gt;</span>
          View Uploaded File
        <span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
      )}
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> FileUpload;
</code></pre>
<p>The <code>FileUpload</code> component above handles the file upload process using the multipart upload method. It splits the file into chunks, uploads each part to the server, and completes the upload process.</p>
<p>The component consists of the following key parts:</p>
<p><code>CHUNK_SIZE</code>: The size of each part in bytes. In this case, we are using 5MB parts.</p>
<p><code>handleFileChange</code>: A function that sets the selected file in the state.</p>
<p><code>handleFileUpload</code>: A function that initiates the multipart upload process by sending the file to the server in parts.</p>
<ul>
<li>It starts the upload process by calling the <strong>/start-upload</strong> endpoint and retrieves the uploadId.</li>
<li>It splits the file into chunks and uploads each part to the server using the <strong>/upload-part</strong> endpoint.</li>
<li>It completes the upload process by calling the <strong>/complete-upload</strong> endpoint with the uploadId and parts array.</li>
</ul>
<p><code>fileUrl</code>: A state variable that stores the URL of the uploaded file.</p>
<p>The component renders an input field for selecting a file, a button to upload the file, and a link to view the uploaded file.</p>
<h3 id="heading-app-component">App Component</h3>
<p>Update the App.js file in the src directory with the following code:</p>
<pre><code class="lang-javascript">
<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">import</span> FileUpload <span class="hljs-keyword">from</span> <span class="hljs-string">"./components/FileUpload"</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"App"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Large File Upload with S3 Multipart Upload<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">FileUpload</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}


<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<p>The App component renders the FileUpload component, which handles the file upload process.</p>
<h3 id="heading-how-to-start-the-frontend">How to Start the Frontend</h3>
<p>To run the frontend, execute the following command in your terminal:</p>
<pre><code class="lang-bash">npm start
</code></pre>
<p>This will start the React development server on port 3000 and open the application in your default web browser.</p>
<h2 id="heading-testing">Testing</h2>
<p>Let's test the application by uploading a large file using the frontend. You should see the file being uploaded in parts and then combined to create the final object on the server inspecting your network tab.</p>
<h3 id="heading-part-upload">Part Upload</h3>
<p>In the image below, the <code>start-upload</code> endpoint is called to initialize and start the upload process. The large file uploaded is broken into chunks and uploaded with the <code>upload-part</code> endpoint. You can see up to 10 or more (depending on the size of each chunk to the total file size).</p>
<p>Each upload part has a unique identifier <code>Etag</code> used for the complete upload.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/06/uplaod-start-parts.png" alt="Image uploading in parts" width="600" height="400" loading="lazy">
<em>Image uploading in parts</em></p>
<h3 id="heading-complete-part-upload">Complete Part Upload</h3>
<p>The last and final step of the process is the <code>complete-upload</code> endpoint where the upload parts are combined to form a single object for the file uploaded.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/06/upload-complete.png" alt="Image uploading in parts" width="600" height="400" loading="lazy">
<em>Image uploads completed</em></p>
<p>You can click on the <code>View Uploaded File</code> to access your uploaded file.</p>
<h2 id="heading-full-code-on-github">Full Code on GitHub</h2>
<p>Click the link below to access the full code on GitHub:</p>
<p><a target="_blank" href="https://github.com/Caesarsage/aws-multipart-uploads-react-node.git">Multipart file uploads with react and NodeJS</a></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this article, we explored how to efficiently handle large files with Amazon S3 multipart upload. We discussed the benefits of using this feature, walked through the process of uploading files in parts, and provided code examples using Node.js and React. </p>
<p>This is a high-level implementation of the multipart upload process, you can further enhance it by adding more features like progress tracking, error handling, and resumable uploads.</p>
<p>By leveraging Amazon S3 multipart upload, you can optimize file uploads in your applications by dividing large files into smaller parts, uploading them independently, and combining them to create the final object. This approach not only enhances upload performance but also adds fault tolerance and flexibility to pause and resume uploads, making it ideal for handling large files over unstable networks.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Optimize AWS Simple Storage Service for Better Performance ]]>
                </title>
                <description>
                    <![CDATA[ S3 is an Amazon Web Service that provides data storage and retrieval in the cloud. ‌‌ This article will discuss the common S3 performance bottlenecks and how they impact the service's overall performance. It will also share some best practices to ser... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-optimize-aws-s3-for-performance/</link>
                <guid isPermaLink="false">66bb58777b4b3dfb68522749</guid>
                
                    <category>
                        <![CDATA[ AWS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ performance ]]>
                    </category>
                
                    <category>
                        <![CDATA[ S3 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ The ERIN ]]>
                </dc:creator>
                <pubDate>Tue, 16 Jan 2024 21:36:44 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/01/s3.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>S3 is an Amazon Web Service that provides data storage and retrieval in the cloud. ‌‌</p>
<p>This article will discuss the common S3 performance bottlenecks and how they impact the service's overall performance. It will also share some best practices to serve as guidelines for optimising the performance of your S3. </p>
<p>By the end, you'll have practical tips to ensure your AWS S3 runs at its best and handles all your data smoothly.</p>
<h2 id="heading-s3-performance-bottlenecks">S3 Performance Bottlenecks</h2>
<p>Here are some common bottlenecks that influence the overall performance of S3:‌‌</p>
<h3 id="heading-network-latency">Network Latency:</h3>
<p>Network latency is the delay incurred when data travels between your application and the S3 storage. This delay arises due to the physical distance, the number of network hops, and the speed at which data can be transmitted. </p>
<p>In simpler terms, it's like the time it takes for your request to reach the S3 server and the response to find its way back. ‌‌</p>
<h3 id="heading-request-processing-time">Request Processing Time:</h3>
<p>When you send a request to Amazon S3, the time it takes for the system to comprehend and fulfill that request is called request processing time. It involves authentication, authorisation, and any necessary computation before responding. ‌‌</p>
<h3 id="heading-server-side-processing">Server-Side Processing:</h3>
<p>Once your request reaches the S3 server, it undergoes processing to fulfill your requested action. This can involve tasks like encryption, access control checks, and other operations that impact the time it takes to serve your data. </p>
<p>While these operations are essential, they can become bottlenecks if not managed efficiently.</p>
<h2 id="heading-impact-of-bottlenecks-on-overall-s3-performance">Impact of Bottlenecks on Overall S3 Performance</h2>
<p>These bottlenecks, individually or collectively, can affect the overall performance of Amazon S3. Here are some of the impacts: ‌‌</p>
<h3 id="heading-slower-data-retrieval">Slower Data Retrieval:</h3>
<p>Network latency and request processing time can lead to delays in retrieving data, affecting the overall speed of your applications relying on S3. Slow data retrieval can impact user experience and application responsiveness, such as waiting for a webpage to load.‌‌</p>
<h3 id="heading-reduced-throughput">Reduced Throughput:</h3>
<p>Bottlenecks, especially in server-side processing and request processing time, can limit the amount of data transferred at a given time. This reduction in throughput can impact the speed at which your applications can read or write data to S3.‌‌</p>
<h3 id="heading-increased-costs">Increased Costs:</h3>
<p>Inefficiencies in data transfer and processing can contribute to increased costs. Longer processing times and additional network usage can result in higher expenses for S3 usage. Identifying and mitigating bottlenecks can lead to cost savings and more efficient resource utilisation.‌‌</p>
<p>Understanding and addressing these common bottlenecks is essential for unlocking the full potential of Amazon S3. The following section will address the best practices to optimise S3 performance and ensure a seamless cloud storage experience.‌‌</p>
<h2 id="heading-best-practices-for-s3-performance-optimization">Best Practices for S3 Performance Optimization</h2>
<h3 id="heading-amazon-s3-transfer-acceleration">Amazon S3 Transfer Acceleration</h3>
<p>Transferring data to and from Amazon S3 may take time due to the physical distance between your location and the S3 server. </p>
<p>Amazon S3 Transfer Acceleration is a bucket-level feature that enables fast, easy, and secure transfers of files over long distances between your client and an S3 bucket. ‌‌</p>
<h4 id="heading-when-to-opt-for-transfer-acceleration">When to opt for transfer acceleration</h4>
<p>You might want to use Transfer Acceleration on a bucket for various reasons:</p>
<ul>
<li>If you're far from the S3 server, Transfer Acceleration is your shortcut. It's like taking a direct flight instead of a connecting one.</li>
<li>For real-time applications or situations where time is of the essence, the speed boost from Transfer Acceleration can be a game-changer.</li>
<li>You need to transfer gigabytes to terabytes of data regularly across continents.</li>
<li>You can't use all of your available bandwidth over the internet when uploading to Amazon S3.‌‌</li>
</ul>
<h4 id="heading-things-to-keep-in-mind">Things to keep in mind</h4>
<ul>
<li><strong>Cost Considerations:</strong> While Transfer Acceleration speeds up your data, it comes with additional costs. </li>
<li>The bucket name used for Transfer Acceleration must be DNS-compliant and not contain periods (".").</li>
<li>Transfer Acceleration must be enabled on the bucket.</li>
<li>After you enable Transfer Acceleration on a bucket, it might take up to 20 minutes before the data transfer speed to the bucket increases.</li>
<li>Transfer Acceleration is only supported in certain following <a target="_blank" href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/transfer-acceleration.html">regions</a>.</li>
<li>You must be the bucket owner to set the transfer acceleration state.‌‌</li>
</ul>
<h4 id="heading-benefits-of-transfer-acceleration">Benefits of Transfer Acceleration</h4>
<ul>
<li><strong>Faster Uploads and Downloads:</strong> Your data gets to its destination quicker, making uploads and downloads snappier. It's like sending a package with express shipping instead of regular mail.</li>
<li><strong>Improved User Experience:</strong> For applications relying on S3, users experience reduced wait times.</li>
<li><strong>Global Reach, Local Touch:</strong> Whether your users are in New York, Tokyo, or spread around the world, they experience speed as if the data is right next door. It's like having a local store in every city.‌‌</li>
</ul>
<p>AWS provides a <a target="_blank" href="https://s3-accelerate-speedtest.s3-accelerate.amazonaws.com/en/accelerate-speed-comparsion.html">Transfer Acceleration speed comparison tool</a> so that a comparison can be made between accelerated and non-accelerated S3 transfer.‌‌‌‌</p>
<h3 id="heading-multi-part-uploads">Multi-part Uploads</h3>
<p>By default, when you upload an object to S3, it is uploaded as a single blob of data in a single stream. S3 uses the PUT operation for uploads, which limits the speed and reliability of the upload because of the single stream of data constraint.</p>
<p>Instead of attempting to upload one colossal file, multi-part uploads divide it into smaller, manageable pieces. The minimum data size for an upload is 100MB, and you shouldn't use a multi-part upload if your data is smaller than this.</p>
<p>An upload can be split into a maximum of 10,000 parts, and each part can range between 5MB and 5GB. Each of the individual parts is isolated from the rest, which means the failure of one part doesn't affect the other parts.‌‌</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/upload2.png" alt="Image" width="600" height="400" loading="lazy">
<em><a target="_blank" href="https://tech.oyorooms.com/efficiently-uploading-large-objects-to-cloud-storage-8712284b0679">Source</a></em></p>
<h4 id="heading-when-to-opt-for-multi-part-uploads">‌‌When to opt for multi-part uploads</h4>
<ul>
<li><strong>Large File Sizes:</strong> Multi-part uploads should become your go-to strategy for dealing with several gigabytes or more files. ‌‌</li>
<li><strong>Unstable Internet Connections:</strong> In areas where the internet connection may not be consistent, multi-part uploads act as a safety net. It's like having a backup plan for your data transfer.‌‌</li>
</ul>
<h4 id="heading-benefits-of-multi-part-uploads">Benefits of multi-part uploads</h4>
<ul>
<li><strong>Faster Uploads:</strong> Multi-part uploads speed up the process. It's like assembling a team to work on different project sections simultaneously, ensuring a quicker completion.</li>
<li><strong>Reliability in Unstable Connections:</strong> In scenarios where your internet connection might have a hiccup, multi-part uploads ensure the upload doesn't fail. It's similar to saving your progress in a game – even if the power goes out, you don't lose everything.</li>
<li><strong>Optimised for Large Files:</strong> When dealing with extensive files, multi-part uploads are like breaking them into manageable chapters. It accelerates the process and simplifies troubleshooting if an issue arises.‌‌</li>
</ul>
<p>To implement multi-part uploads, you can find the detailed steps via <a target="_blank" href="https://aws.amazon.com/premiumsupport/knowledge-center/s3-multipart-upload-cli/">AWS Documentation</a> ‌‌‌‌</p>
<h3 id="heading-measuring-performance-and-monitoring">Measuring Performance and Monitoring</h3>
<p>To ensure your Amazon S3 storage operates at its best, keeping an eye on its performance is like giving it a regular check-up. Here's a straightforward guide on how to measure performance and monitor your S3 environment efficiently:</p>
<ul>
<li><strong>Network Throughput, CPU, and DRAM:</strong> Imagine S3 as a bustling marketplace. To optimise, check the "foot traffic" (network throughput), the "workers' efficiency" (CPU), and the "storage capacity" (DRAM). If any of these are congested, it might be time to consider different "worker" types – or, in AWS terms, Amazon EC2 instance types.‌‌</li>
<li>Use HTTP analysis tools to ensure your data is moving swiftly and efficiently.‌‌</li>
<li>Monitor the number of error 503 (Slow Down) Status Error Responses by using Amazon Cloudwatch, Amazon S3 Storage Lens, and Amazon S3 Server Access Logging.‌‌</li>
<li><strong>Use CloudWatch for Insights on your S3 Performance:</strong> CloudWatch uses metrics and dimensions to visualise S3's operational health. Metrics are the data points, such as the number of requests made to an S3 bucket, and dimensions represent a key-value pair of the identity of the metric, helping you filter and focus on specific aspects, like testing environments. CloudWatch presents your data visually, like a set of easy-to-read charts. It's like a health report for your S3. Alarms act as your alert system, notifying you if something needs immediate attention.‌‌</li>
</ul>
<h3 id="heading-use-byte-range-fetches">Use Byte-Range Fetches</h3>
<p>Optimising the retrieval of objects from Amazon S3 involves a technique known as Byte-Range Fetches. Utilising the Range HTTP header within a GET Object request, you can selectively fetch specific byte ranges from an object, transmitting only the designated portion.‌‌</p>
<p>This approach is efficient when dealing with large objects. Fetching smaller ranges boosts aggregate throughput and facilitates more efficient retry times in case of interrupted requests. </p>
<p>For seamless integration with multi-part uploads, it is recommended to align the byte-range sizes with the sizes of the parts used during the upload process. This alignment ensures optimal performance, and GET requests can directly target individual parts, such as using the syntax <code>GET ?partNumber=N</code>. ‌‌‌‌</p>
<h3 id="heading-cloudfront">CloudFront</h3>
<p>CloudFront is a global content delivery network (CDN) that provides viewers with swift content delivery marked by low latency and high transfer speeds. </p>
<p>Leveraging a network of strategically positioned edge locations, CloudFront intelligently caches copies of your S3 content, ensuring proximity to the viewer. This proximity reduces the distance travelled by cached content requests and responses compared to direct routes to your S3 region, resulting in significantly improved performance. </p>
<p>For content not cached, CloudFront seamlessly retrieves it from S3, caching it as necessary. ‌‌</p>
<p>CloudFront offers security enhancements by restricting direct access to S3 and allowing content access exclusively through CloudFront. This restriction is implemented using an origin access identity (OAI). </p>
<p>Additionally, HTTPS encryption can be enforced for situations requiring encryption-in-transit between CloudFront and the viewer, adding an extra layer of security to the content delivery process.‌‌</p>
<p>To achieve high performance, CloudFront incorporates various optimisations, including TLS session resumption, TCP fast open, OCSP stapling, S2N, and request collapsing. It supports a range of HTTP protocols, including HTTP/1.0, HTTP/1.1, HTTP/2, and HTTP/3.‌‌‌‌</p>
<h3 id="heading-s3-select">S3 Select</h3>
<p>S3 Select offers a streamlined approach to fetching specific data from object contents using SQL expressions. It enhances precision in data retrieval, contributes to cost savings, and improves overall performance by minimising the amount of data transferred.‌‌</p>
<p>S3 Select is versatile, operating seamlessly on objects stored in CSV, JSON, and Apache Parquet formats. This flexibility accommodates various data structures and types. It extends its capabilities to objects compressed with GZip and BZip2, ensuring that even compressed CSV and JSON files can be efficiently processed.‌‌</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/s3-select.png" alt="Image" width="600" height="400" loading="lazy">
<em>A diagram that illustrates S3 select method (from <a target="_blank" href="https://aws.amazon.com/blogs/aws/s3-glacier-select/">here</a>).</em></p>
<p>‌‌By employing S3 Select, unnecessary data transfer is curtailed. This reduction in transferred data directly translates to cost savings, as only the required information is transmitted. </p>
<p>The streamlined data retrieval process enhances overall performance by ensuring that the SQL expressions used in S3 Select efficiently fetch the necessary data, contributing to a more responsive and agile system.‌‌‌‌</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Optimising Amazon S3 performance is essential for faster storage and retrieval of data in the cloud. </p>
<p>Identifying and addressing common bottlenecks, such as network latency and server-side processing delays, forms the foundation of a high-performing S3 environment. </p>
<p>Then, implementing best practices like multi-part uploads, Amazon S3 Transfer Acceleration, and CloudFront integration streamlines operations and enhances efficiency. </p>
<p>The power of S3 Select provides precision and agility in data retrieval and monitoring tools like CloudWatch to respond to 503 error signals and keep S3 health in check. </p>
<p>As the cloud environment changes, adopting these practices ensures not just optimal S3 performance but a resilient and future-ready cloud storage infrastructure.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Stream File Uploads to S3 Object Storage and Reduce Costs ]]>
                </title>
                <description>
                    <![CDATA[ By Austin Gil To support file uploads in your application, you will have to learn how to send files from the frontend and receive files on the backend.  This tutorial is going to take a step back and explore architectural changes that'll help you red... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/stream-file-uploads-to-s3-object-storage-and-reduce-costs/</link>
                <guid isPermaLink="false">66d45d9ecc7f04d2549a3732</guid>
                
                    <category>
                        <![CDATA[ Cloud Computing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ node js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ S3 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Tue, 23 May 2023 19:51:05 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/05/pexels-pixabay-219717.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Austin Gil</p>
<p>To support file uploads in your application, you will have to learn how to <a target="_blank" href="https://austingil.com/uploading-files-with-html/">send files from the frontend</a> and <a target="_blank" href="https://austingil.com/file-uploads-in-node/">receive files on the backend</a>. </p>
<p>This tutorial is going to take a step back and explore architectural changes that'll help you reduce costs when adding file uploads to your applications.</p>
<h2 id="heading-heres-what-well-cover">Here's what we'll cover:</h2>
<ol>
<li><a class="post-section-overview" href="#heading-what-is-object-storage">What is Object Storage?</a></li>
<li><a class="post-section-overview" href="#heading-what-is-s3">What is S3?</a></li>
<li><a class="post-section-overview" href="#id=&quot;start-with-an-existing-node-js-application&quot;">Start with an Existing Node.js Application</a></li>
<li><a class="post-section-overview" href="#heading-set-up-the-s3-client">Set Up the S3 Client</a></li>
<li><a class="post-section-overview" href="#heading-how-to-modify-formidable">How to Modify formidable</a></li>
<li><a class="post-section-overview" href="#heading-walkthrough-of-the-whole-flow">Walkthrough of the Whole Flow</a></li>
<li><a class="post-section-overview" href="#heading-caveats">Caveats</a></li>
<li><a class="post-section-overview" href="#heading-closing-thoughts">Closing Thoughts</a></li>
</ol>
<p>And here's a video walkthrough you can use to supplement this tutorial if you like:</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/cJ6IuSJabXk" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<p>Before we get too far, you should already be familiar with sending and receiving a <code>multipart/form-data</code> request, parsing the request, accessing the file stream, and writing that file to the disk <strong>on the application server</strong>.</p>
<p>Note that the flow described above writes files to the application server. This is pretty common, but there are some issues with this approach.</p>
<p>First, this approach doesn’t work for distributed systems that may rely on several different machines. If a user uploads a file it can be hard (or impossible) to know which machine received the request, and therefore, where the file is saved. This is especially true if you’re using serverless or edge compute.</p>
<p>Secondly, storing uploads on the application server can lead to the server to run out of disk space. At which point, we’d have to upgrade our server. That could be much more expensive than other cost-effective solutions.</p>
<p>And that’s where <a target="_blank" href="https://www.linode.com/products/object-storage/">Object Storage</a> comes in.</p>
<h2 id="heading-what-is-object-storage">What is Object Storage?</h2>
<p>You can think of Object Storage like a folder on a computer. You can put any files (aka “objects”) you want in it, but the folders (aka “buckets”) live within a cloud service provider. You can also access files via URL.</p>
<p>Object Storage provides a couple of benefits:</p>
<ul>
<li>It’s a single, central place to store and access all of your uploads.</li>
<li>It’s designed to be highly available, easily scalable, and super cost-effective.</li>
</ul>
<p>For example, if you consider <a target="_blank" href="https://www.linode.com/products/shared/">shared CPU servers</a>, you could run an application for $5/month and get 25 GB of disk space. If your server starts running out of space, you could upgrade your server to get an additional 25 GB, but that’s going to cost you $7/month more.</p>
<p>Alternatively, you could put that money towards Object Storage and you would get 250 GB for $5/month. So 10 times more storage space for less cost.</p>
<p>Of course, there are other reasons to upgrade your application server. You may need more RAM or CPU, but if we’re talking purely about disk space, Object Storage is a much cheaper solution.</p>
<p>With that in mind, the rest of this article will cover connecting an existing Node.js application to an Object Storage provider. We’ll use <a target="_blank" href="https://github.com/node-formidable/formidable">formidable</a> to parse multipart requests, but configure it to upload files to Object Storage instead of writing to disk.</p>
<p>If you want to follow along, you will need to have an Object Storage bucket set up, as well as the access keys. Any S3-compatible Object Storage provider should work. </p>
<p>Today, I’ll be using <a target="_blank" href="https://bit.ly/austinode">Akamai’s cloud computing services</a> (formerly Linode). If you want to do the same, <a target="_blank" href="https://www.linode.com/docs/products/storage/object-storage/get-started/">here’s a guide that shows you how to get going</a>.</p>
<p><a target="_blank" href="https://bit.ly/austinode">And here’s a link to get $100 in free credits for 60 days</a>.</p>
<h2 id="heading-what-is-s3">What is S3?</h2>
<p>We'll get hands on with code shortly, but before we do, there’s one more concept that I should explain: S3. S3 stands for “Simple Storage Service”, and it’s an Object Storage product originally developed at AWS.</p>
<p>Along with their product, AWS came up with a standard communication protocol for interacting with their Object Storage solution.</p>
<p>As more companies started offering Object Storage services, they decided to also adopt the same S3 communication protocol for their Object Storage service, and S3 became a standard.</p>
<p>As a result, we have more options to choose from for Object Storage providers and fewer options to dig through for tooling. We can use the same libraries (maintained by AWS) with other providers. That’s great news because it means the code we write today should work across any S3-compatible service.</p>
<p>Today, we'll be working with a Node.js application and the libraries we’ll need are <a target="_blank" href="https://www.npmjs.com/package/@aws-sdk/client-s3"><code>@aws-sdk/client-s3</code></a> and <a target="_blank" href="https://www.npmjs.com/package/@aws-sdk/lib-storage"><code>@aws-sdk/lib-storage</code></a>:</p>
<pre><code>npm install @aws-sdk/client-s3 @aws-sdk/lib-storage
</code></pre><p>These libraries will help us upload objects into our buckets.</p>
<p>Okay, let’s write some code!</p>
<h2 id="heading-start-with-an-existing-nodejs-application">Start with an Existing Node.js Application</h2>
<p>We’ll start with an example <a target="_blank" href="https://nuxt.com/">Nuxt.js</a> event handler that writes files to disk using formidable. It checks if a request contains <code>multipart/form-data</code> and if so, it passes the underlying Node.js request object (aka <code>IncomingMessage</code>) to a custom function <code>parseMultipartNodeRequest</code>. Since this function uses the Node.js request, it will work in any Node.js environment and tools like formidable.</p>
<pre><code><span class="hljs-keyword">import</span> formidable <span class="hljs-keyword">from</span> <span class="hljs-string">'formidable'</span>;

<span class="hljs-comment">/* global defineEventHandler, getRequestHeaders, readBody */</span>

<span class="hljs-comment">/**
 * @see https://nuxt.com/docs/guide/concepts/server-engine
 * @see https://github.com/unjs/h3
 */</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineEventHandler(<span class="hljs-keyword">async</span> (event) =&gt; {
  <span class="hljs-keyword">let</span> body;
  <span class="hljs-keyword">const</span> headers = getRequestHeaders(event);

  <span class="hljs-keyword">if</span> (headers[<span class="hljs-string">'content-type'</span>]?.includes(<span class="hljs-string">'multipart/form-data'</span>)) {
    body = <span class="hljs-keyword">await</span> parseMultipartNodeRequest(event.node.req);
  } <span class="hljs-keyword">else</span> {
    body = <span class="hljs-keyword">await</span> readBody(event);
  }
  <span class="hljs-built_in">console</span>.log(body);

  <span class="hljs-keyword">return</span> { <span class="hljs-attr">ok</span>: <span class="hljs-literal">true</span> };
});

<span class="hljs-comment">/**
 * @param {import('http').IncomingMessage} req
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">parseMultipartNodeRequest</span>(<span class="hljs-params">req</span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> form = formidable({ <span class="hljs-attr">multiples</span>: <span class="hljs-literal">true</span> });
    form.parse(req, <span class="hljs-function">(<span class="hljs-params">error, fields, files</span>) =&gt;</span> {
      <span class="hljs-keyword">if</span> (error) {
        reject(error);
        <span class="hljs-keyword">return</span>;
      }
      resolve({ ...fields, ...files });
    });
  });
}
</code></pre><p>We’re going to modify this code to send the files to an S3 bucket instead of writing them to disk.</p>
<h2 id="heading-set-up-the-s3-client">Set Up the S3 Client</h2>
<p>The first thing we need to do is set up an S3 Client to make the upload requests for us, so we don’t have to write them manually. We’ll import the <code>S3Client</code> constructor from <code>@aws-sdk/client-s3</code> as well as the <code>Upload</code> command from <code>@aws-sdk/lib-storage</code>. We’ll also import Node’s <code>stream</code> module to use later on.</p>
<pre><code><span class="hljs-keyword">import</span> stream <span class="hljs-keyword">from</span> <span class="hljs-string">'node:stream'</span>;
<span class="hljs-keyword">import</span> { S3Client } <span class="hljs-keyword">from</span> <span class="hljs-string">'@aws-sdk/client-s3'</span>;
<span class="hljs-keyword">import</span> { Upload } <span class="hljs-keyword">from</span> <span class="hljs-string">'@aws-sdk/lib-storage'</span>;
</code></pre><p>Next, we need to configure our client using our S3 bucket <strong>endpoint</strong>, <a target="_blank" href="https://www.linode.com/docs/products/storage/object-storage/guides/access-keys/"><strong>access key</strong></a>, <a target="_blank" href="https://www.linode.com/docs/products/storage/object-storage/guides/access-keys/"><strong>secret access key</strong></a>, and <strong>region</strong>. Again, you should already have set up an S3 bucket and know where to find this information. If not, <a target="_blank" href="https://www.linode.com/docs/products/storage/object-storage/get-started/">check out this guide</a> (<a target="_blank" href="https://bit.ly/austinode">$100 credit</a>).</p>
<p>I like to store this information in environment variables and not hard-code the configuration into the source code. We can access those variables using <code>process.env</code> to use in our application.</p>
<pre><code><span class="hljs-keyword">const</span> { S3_URL, S3_ACCESS_KEY, S3_SECRET_KEY, S3_REGION } = process.env;
</code></pre><p>If you’ve never used environment variables, they’re a good place for us to put secret information such as access credentials. You can <a target="_blank" href="https://nodejs.dev/en/learn/how-to-read-environment-variables-from-nodejs/">read more about them here</a>.</p>
<p>With our variables set up, I can now instantiate the S3 Client we’ll use to communicate to our bucket.</p>
<pre><code><span class="hljs-keyword">const</span> s3Client = <span class="hljs-keyword">new</span> S3Client({
  <span class="hljs-attr">endpoint</span>: <span class="hljs-string">`https://<span class="hljs-subst">${S3_URL}</span>`</span>,
  <span class="hljs-attr">credentials</span>: {
    <span class="hljs-attr">accessKeyId</span>: S3_ACCESS_KEY,
    <span class="hljs-attr">secretAccessKey</span>: S3_SECRET_KEY,
  },
  <span class="hljs-attr">region</span>: S3_REGION,
});
</code></pre><p>It’s worth pointing out that the endpoint needs to include the HTTPS protocol. In Akamai’s Object Storage dashboard, when you copy the bucket URL, but it doesn’t include the protocol (<code>bucket-name.bucket-region.linodeobjects.com</code>). So I just add the prefix here.</p>
<p>With our S3 client configured, we can start using it.</p>
<h2 id="heading-how-to-modify-formidable">How to Modify formidable</h2>
<p>In our application, we’re passing any multipart Node request into our custom function, <code>parseMultipartNodeRequest</code>. This function returns a Promise and passes the request to formidable, which parses the request, writes files to the disk, and resolves the promise with the form fields data and files data.</p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">parseMultipartNodeRequest</span>(<span class="hljs-params">req</span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> form = formidable({ <span class="hljs-attr">multiples</span>: <span class="hljs-literal">true</span> });
    form.parse(req, <span class="hljs-function">(<span class="hljs-params">error, fields, files</span>) =&gt;</span> {
      <span class="hljs-keyword">if</span> (error) {
        reject(error);
        <span class="hljs-keyword">return</span>;
      }
      resolve({ ...fields, ...files });
    });
  });
}
</code></pre><p>This is the part that needs to change. Instead of processing the request and writing files to disk, we want to pipe file streams to an S3 upload request. So as each file chunk is received, it’s passed through our handler to the S3 upload.</p>
<p>We’ll still return a promise and use formidable to parse the form, but we have to change formidable’s <a target="_blank" href="https://github.com/node-formidable/formidable#options">configuration options</a>. We’ll set the <code>fileWriteStreamHandler</code> option to a function called <code>fileWriteStreamHandler</code> that we’ll write shortly.</p>
<pre><code><span class="hljs-comment">/** <span class="hljs-doctag">@param <span class="hljs-type">{import('formidable').File}</span> </span>file */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fileWriteStreamHandler</span>(<span class="hljs-params">file</span>) </span>{
  <span class="hljs-comment">// TODO</span>
}
<span class="hljs-keyword">const</span> form = formidable({
  <span class="hljs-attr">multiples</span>: <span class="hljs-literal">true</span>,
  <span class="hljs-attr">fileWriteStreamHandler</span>: fileWriteStreamHandler,
});
</code></pre><p>Here’s what their documentation says about <code>fileWriteStreamHandler</code>:</p>
<blockquote>
<p><code>options.fileWriteStreamHandler</code> <strong>{function}</strong> – default <code>null</code>, which by default writes to host machine file system every file parsed; The function should return an instance of a <a target="_blank" href="https://nodejs.org/api/stream.html#stream_class_stream_writable">Writable stream</a> that will receive the uploaded file data. With this option, you can have any custom behavior regarding where the uploaded file data will be streamed for. If you are looking to write the file uploaded in other types of cloud storages (AWS S3, Azure blob storage, Google cloud storage) or private file storage, this is the option you’re looking for. When this option is defined the default behavior of writing the file in the host machine file system is lost.</p>
</blockquote>
<p>As formidable parses each chunk of data from the request, it will pipe that chunk into the Writable stream that’s returned from this function. So our <code>fileWriteStreamHandler</code> function is where the magic happens.</p>
<p>Before we write the code, let’s understand some things:</p>
<ol>
<li>This function must return a <a target="_blank" href="https://nodejs.org/api/stream.html#stream_class_stream_writable">Writable stream</a> to write each upload chunk to.</li>
<li>It <strong>also</strong> needs to pipe each chunk of data to an S3 Object Storage.</li>
<li>We can use the <code>Upload</code> command from <code>@aws-sdk/lib-storage</code> to create the request.</li>
<li>The request body can be a stream, but it must be a <a target="_blank" href="https://nodejs.org/api/stream.html#stream_class_stream_readable">Readable stream</a>, not a Writable stream.</li>
<li>A <a target="_blank" href="https://nodejs.org/api/stream.html#stream_class_stream_passthrough">Passthrough stream</a> can be used as <strong>both</strong> a Readable and Writable stream.</li>
<li>Each request formidable will parse may contain multiple files, so we may need to track multiple S3 upload requests.</li>
<li><code>fileWriteStreamHandler</code> receives one parameter of type <a target="_blank" href="https://github.com/node-formidable/formidable#file"><code>formidable.File</code> interface</a> with properties like <code>originalFilename</code>, <code>size</code>, <code>mimetype</code>, and more.</li>
</ol>
<p>OK, now let’s write the code. We’ll start with an <code>Array</code> to store and track all the S3 upload requests outside the scope of <code>fileWriteStreamHandler</code>. </p>
<p>Inside <code>fileWriteStreamHandler</code>, we’ll create the <code>Passthrough</code> stream that will serve as both the Readable body of the S3 upload and the Writable return value of this function. </p>
<p>We’ll create the <code>Upload</code> request using the S3 libraries, and tell it our bucket name, the object key (which can include folders), the object Content-Type, the Access Control Level for this object, and the <code>Passthrough</code> stream as the request body. </p>
<p>We’ll instantiate the request using <code>Upload.done()</code> and add the returned <code>Promise</code> to our tracking <code>Array</code>. We might want to add the response <code>Location</code> property to the <code>file</code> object when the upload completes, so we can use that information later on. </p>
<p>Lastly, we’ll return the <code>Passthrough</code> stream from this function:</p>
<pre><code><span class="hljs-comment">/** <span class="hljs-doctag">@type <span class="hljs-type">{Promise&lt;any&gt;[]}</span> </span>*/</span>
<span class="hljs-keyword">const</span> s3Uploads = [];

<span class="hljs-comment">/** @param {import('formidable').File} file */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fileWriteStreamHandler</span>(<span class="hljs-params">file</span>) </span>{
  <span class="hljs-keyword">const</span> body = <span class="hljs-keyword">new</span> stream.PassThrough();
  <span class="hljs-keyword">const</span> upload = <span class="hljs-keyword">new</span> Upload({
    <span class="hljs-attr">client</span>: s3Client,
    <span class="hljs-attr">params</span>: {
      <span class="hljs-attr">Bucket</span>: <span class="hljs-string">'austins-bucket'</span>,
      <span class="hljs-attr">Key</span>: <span class="hljs-string">`files/<span class="hljs-subst">${file.originalFilename}</span>`</span>,
      <span class="hljs-attr">ContentType</span>: file.mimetype,
      <span class="hljs-attr">ACL</span>: <span class="hljs-string">'public-read'</span>,
      <span class="hljs-attr">Body</span>: body,
    },
  });
  <span class="hljs-keyword">const</span> uploadRequest = upload.done().then(<span class="hljs-function">(<span class="hljs-params">response</span>) =&gt;</span> {
    file.location = response.Location;
  });
  s3Uploads.push(uploadRequest);
  <span class="hljs-keyword">return</span> body;
}
</code></pre><p>A couple of things to note:</p>
<ul>
<li><code>Key</code> is the name and location where the object will exist. It can include folders that will be created if they do not currently exist. If a file exists with the same name and location, it will be overwritten (fine for me today). You can avoid collisions by using hashed names or timestamps.</li>
<li><code>ContentType</code> is not required, but it’s helpful to include. It allows browsers to create the downloaded response appropriately based on Content-Type.</li>
<li><code>ACL</code>: is also optional, but by default, every object is private. If you want people to be able to access the files via URL (like an <code>&lt;img&gt;</code> element), you’ll want to make it public.</li>
<li>Although <code>@aws-sdk/client-s3</code> supports uploads, you need <code>@aws-sdk/lib-storage</code> to support Readable streams.</li>
<li>You can read more about the parameters <a target="_blank" href="https://www.npmjs.com/package/@aws-sdk/client-s3">on NPM</a>.</li>
</ul>
<p>This way, formidable becomes the plumbing that connects the incoming client request to the S3 upload request.</p>
<p>Now there’s just one more change to make. We are keeping track of all the upload requests, but we aren’t waiting for them to finish.</p>
<p>We can fix that by modifying the <code>parseMultipartNodeRequest</code> function. It should continue to use formidable to parse the client request, but instead of resolving the promise immediately, we can use <code>Promise.all</code> to wait until all the upload requests have resolved.</p>
<p>The whole function looks like this:</p>
<pre><code><span class="hljs-comment">/**
 * <span class="hljs-doctag">@param <span class="hljs-type">{import('http').IncomingMessage}</span> <span class="hljs-variable">req</span></span>
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">parseMultipartNodeRequest</span>(<span class="hljs-params">req</span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =&gt;</span> {
    <span class="hljs-comment">/** @type {Promise&lt;any&gt;[]} */</span>
    <span class="hljs-keyword">const</span> s3Uploads = [];

    <span class="hljs-comment">/** @param {import('formidable').File} file */</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fileWriteStreamHandler</span>(<span class="hljs-params">file</span>) </span>{
      <span class="hljs-keyword">const</span> body = <span class="hljs-keyword">new</span> PassThrough();
      <span class="hljs-keyword">const</span> upload = <span class="hljs-keyword">new</span> Upload({
        <span class="hljs-attr">client</span>: s3Client,
        <span class="hljs-attr">params</span>: {
          <span class="hljs-attr">Bucket</span>: <span class="hljs-string">'austins-bucket'</span>,
          <span class="hljs-attr">Key</span>: <span class="hljs-string">`files/<span class="hljs-subst">${file.originalFilename}</span>`</span>,
          <span class="hljs-attr">ContentType</span>: file.mimetype,
          <span class="hljs-attr">ACL</span>: <span class="hljs-string">'public-read'</span>,
          <span class="hljs-attr">Body</span>: body,
        },
      });
      <span class="hljs-keyword">const</span> uploadRequest = upload.done().then(<span class="hljs-function">(<span class="hljs-params">response</span>) =&gt;</span> {
        file.location = response.Location;
      });
      s3Uploads.push(uploadRequest);
      <span class="hljs-keyword">return</span> body;
    }
    <span class="hljs-keyword">const</span> form = formidable({
      <span class="hljs-attr">multiples</span>: <span class="hljs-literal">true</span>,
      <span class="hljs-attr">fileWriteStreamHandler</span>: fileWriteStreamHandler,
    });
    form.parse(req, <span class="hljs-function">(<span class="hljs-params">error, fields, files</span>) =&gt;</span> {
      <span class="hljs-keyword">if</span> (error) {
        reject(error);
        <span class="hljs-keyword">return</span>;
      }
      <span class="hljs-built_in">Promise</span>.all(s3Uploads)
        .then(<span class="hljs-function">() =&gt;</span> {
          resolve({ ...fields, ...files });
        })
        .catch(reject);
    });
  });
}
</code></pre><p>The resolved <code>files</code> value will also contain the <code>location</code> property we included, pointing to the Object Storage URL.</p>
<h2 id="heading-walkthrough-of-the-whole-flow">Walkthrough of the Whole Flow</h2>
<p>We covered a lot, and I think it’s a good idea to review how everything works together. If we look back at the original event handler, we can see that any <code>multipart/form-data</code> request will be received and passed to our <code>parseMultipartNodeRequest</code> function. The resolved value from this function will be logged to the console:</p>
<pre><code><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineEventHandler(<span class="hljs-keyword">async</span> (event) =&gt; {
  <span class="hljs-keyword">let</span> body;
  <span class="hljs-keyword">const</span> headers = getRequestHeaders(event);

  <span class="hljs-keyword">if</span> (headers[<span class="hljs-string">'content-type'</span>]?.includes(<span class="hljs-string">'multipart/form-data'</span>)) {
    body = <span class="hljs-keyword">await</span> parseMultipartNodeRequest(event.node.req);
  } <span class="hljs-keyword">else</span> {
    body = <span class="hljs-keyword">await</span> readBody(event);
  }
  <span class="hljs-built_in">console</span>.log(body);

  <span class="hljs-keyword">return</span> { <span class="hljs-attr">ok</span>: <span class="hljs-literal">true</span> };
});
</code></pre><p>With that in mind, let’s break down what happens if I want to upload <a target="_blank" href="https://www.instagram.com/p/CmUxW6jDmWO/">a cute photo of Nugget making a big ol’ yawn</a>.</p>
<ol>
<li>For the browser to send the file as binary data, it needs to make a <code>multiplart/form-data</code> request with an <a target="_blank" href="https://austingil.com/uploading-files-with-html/">HTML form</a> or <a target="_blank" href="https://austingil.com/upload-files-with-javascript/">with JavaScript</a>.</li>
<li>Our Nuxt.js <a target="_blank" href="https://austingil.com/file-uploads-in-node/">application receives the <code>multipart/form-data</code></a> and passes the underlying Node.js request object to our custom <code>parseMultipartNodeRequest</code> function.</li>
<li><code>parseMultipartNodeRequest</code> returns a <code>Promise</code> that will eventually be resolved with the data. Inside that <code>Promise</code>, we instantiate the formidable library and pass the request object to formidable for parsing.</li>
<li>As formidable is parsing the request when it comes across a file, it writes the chunks of data from the file stream to the <code>Passthrough</code> stream that’s returned from the <code>fileWriteStreamHandler</code> function.</li>
<li>Inside the <code>fileWriteStreamHandler</code> we also set up a request to upload the file to our S3-compatible bucket, and we use the same <code>Passthrough</code> stream as the body of the request. So as formidable writes chunks of file data to the <code>Passthrough</code> stream, they are also read by the S3 upload request.</li>
<li>Once formidable has finished parsing the request, all the chunks of data from the file streams are taken care of, and we wait for the list of S3 requests to finish uploading.</li>
<li>After all that is done, we resolve the <code>Promise</code> from <code>parseMultipartNodeRequest</code> with the modified data from formidable. The <code>body</code> variable is assigned to the resolved value.</li>
<li>The data representing the fields and files (not the files themselves) are logged to the console.</li>
</ol>
<p>So now, if our original upload request contained a single field called “file1” with the photo of Nugget, we might see something like this:</p>
<pre><code>{
  <span class="hljs-attr">file1</span>: {
    <span class="hljs-attr">_events</span>: [<span class="hljs-built_in">Object</span>: <span class="hljs-literal">null</span> prototype] { <span class="hljs-attr">error</span>: [<span class="hljs-built_in">Function</span> (anonymous)] },
    <span class="hljs-attr">_eventsCount</span>: <span class="hljs-number">1</span>,
    <span class="hljs-attr">_maxListeners</span>: <span class="hljs-literal">undefined</span>,
    <span class="hljs-attr">lastModifiedDate</span>: <span class="hljs-literal">null</span>,
    <span class="hljs-attr">filepath</span>: <span class="hljs-string">'/tmp/93374f13c6cab7a01f7cb5100'</span>,
    <span class="hljs-attr">newFilename</span>: <span class="hljs-string">'93374f13c6cab7a01f7cb5100'</span>,
    <span class="hljs-attr">originalFilename</span>: <span class="hljs-string">'nugget.jpg'</span>,
    <span class="hljs-attr">mimetype</span>: <span class="hljs-string">'image/jpeg'</span>,
    <span class="hljs-attr">hashAlgorithm</span>: <span class="hljs-literal">false</span>,
    <span class="hljs-attr">createFileWriteStream</span>: [<span class="hljs-built_in">Function</span>: fileWriteStreamHandler],
    <span class="hljs-attr">size</span>: <span class="hljs-number">82298</span>,
    <span class="hljs-attr">_writeStream</span>: PassThrough {
      <span class="hljs-attr">_readableState</span>: [ReadableState],
      <span class="hljs-attr">_events</span>: [<span class="hljs-built_in">Object</span>: <span class="hljs-literal">null</span> prototype],
      <span class="hljs-attr">_eventsCount</span>: <span class="hljs-number">6</span>,
      <span class="hljs-attr">_maxListeners</span>: <span class="hljs-literal">undefined</span>,
      <span class="hljs-attr">_writableState</span>: [WritableState],
      <span class="hljs-attr">allowHalfOpen</span>: <span class="hljs-literal">true</span>,
      [<span class="hljs-built_in">Symbol</span>(kCapture)]: <span class="hljs-literal">false</span>,
      [<span class="hljs-built_in">Symbol</span>(kCallback)]: <span class="hljs-literal">null</span>
    },
    <span class="hljs-attr">hash</span>: <span class="hljs-literal">null</span>,
    <span class="hljs-attr">location</span>: <span class="hljs-string">'https://austins-bucket.us-southeast-1.linodeobjects.com/files/nugget.jpg'</span>,
    [<span class="hljs-built_in">Symbol</span>(kCapture)]: <span class="hljs-literal">false</span>
  }
}
</code></pre><p>It looks very similar to the object formidable returns when it writes directly to disk. But this time it has an extra property, <code>location</code>, which is the Object Storage URL for our uploaded file.</p>
<p>Throw that sucker in your browser and what do you get?</p>
<p><img src="https://austingil.com/wp-content/uploads/image-65-1080x608.png" alt="Screenshot of my browser showing a cute photo of Nugget making a big yawn, and there's a box highlighting the URL from Akamai Object Storage." width="1080" height="608" loading="lazy"></p>
<p>That’s right! A cute photo of Nugget making a big ol’ yawn 🥰</p>
<p>I can also go to my bucket in my <a target="_blank" href="https://cloud.linode.com/object-storage">Object Storage dashboard</a> and see that I now have a folder called “files” containing a file called “nugget.jpg”.</p>
<p><img src="https://austingil.com/wp-content/uploads/image-62-1080x568.png" alt="Screenshot of my Akamai Object Storage dashboard showing &quot;nugget.jpg&quot; inside the &quot;files&quot; folder inside the &quot;austins-bucket&quot; Object Storage instance." width="1080" height="568" loading="lazy"></p>
<h2 id="heading-caveats">Caveats</h2>
<p>I would be remiss if I didn’t mention the following. (In fact, I <strong>was</strong> remiss because I didn’t mention it until after someone pointed it out to me 🤣)</p>
<p>Streaming uploads through your backend to Object Storage is not the only way to upload files to S3. You can also use <strong>signed URLs</strong>. </p>
<p>Signed URLs are basically the same URL in the bucket where the file will live, but they include an authentication signature that can be used by anyone to upload a file, as long as the signature has not expired (usually quite soon).</p>
<p>Here’s how the flow generally works:</p>
<ol>
<li>Frontend makes a request to the backend for a signed URL.</li>
<li>Backend makes an authenticated request to the Object Storage provider for a signed URL with a given expiry.</li>
<li>Object Storage provider provides a signed URL to the backend.</li>
<li>Backend returns the signed URL to the frontend.</li>
<li>Frontend uploads the file directly to Object Storage thanks to the signed URL.</li>
<li>Optional: Frontend may make another request to the Backend if you need to update a database that the upload completed.</li>
</ol>
<p>This flow requires a little more choreography than Frontend -&gt; Backend -&gt; Object Storage, but it has some benefits.</p>
<ul>
<li>It moves work off your servers, which can reduce load and improve performance.</li>
<li>It moves the file upload bandwidth off your server. If you pay for ingress and have several large file uploads all the time, this could add up.</li>
</ul>
<p>It also comes with its own costs.</p>
<ul>
<li>You have much less control over what users can upload. This might include malware.</li>
<li>If you need to perform functions on the files like optimizing, you can’t do that with signed URLs.</li>
<li>The complex flow makes it much harder to build an upload flow with progressive enhancement in mind.</li>
</ul>
<p>As with most things in web development, there is not one right solution. It will largely depend on your use case. I like going through my backend, so I have more control over the files and I can simplify the frontend.</p>
<p>I wanted to share this streaming option, largely because there is hardly any content out there about streaming. Most content uses signed URLs (maybe I’m missing something). If you’d like to learn more about using signed URLs, <a target="_blank" href="https://www.linode.com/docs/products/storage/object-storage/guides/urls/#signed-urls">here is some documentation</a> and <a target="_blank" href="https://dev.to/gathoni/how-to-upload-an-image-to-a-linode-storage-bucket-using-a-pre-signed-url-1ba7">here’s a handy tutorial</a> by <a target="_blank" href="https://twitter.com/remigathoni">Mary Gathoni</a>.</p>
<h2 id="heading-closing-thoughts">Closing Thoughts</h2>
<p>Okay, we covered a lot today. I hope it all made sense. If not, feel free to reach out to me with questions. Also, reach out and let me know if you got it working in your own application.</p>
<p>I’d love to hear from you, because using Object Storage is an excellent architectural decision if you need a single, cost-effective place to store files.</p>
<p>Thank you so much for reading. If you liked this article, and want to support me, the best ways to do so are to <a target="_blank" href="https://twitter.com/share?via=heyAustinGil">share it</a>, <a target="_blank" href="https://austingil.com/newsletter/">sign up for my newsletter</a>, and <a target="_blank" href="https://twitter.com/heyAustinGil">follow me on Twitter</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Everything You Need to Know About AWS S3 ]]>
                </title>
                <description>
                    <![CDATA[ This article will provide an in-depth introduction to AWS S3 — the secure, scalable, and super cheap storage service from Amazon Web Services. If you have ever worked as a developer, you have likely come across file storage use cases. From simple ima... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/everything-you-need-to-know-about-aws-s3/</link>
                <guid isPermaLink="false">66d035c2871ae63f179f6b91</guid>
                
                    <category>
                        <![CDATA[ AWS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ S3 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Security ]]>
                    </category>
                
                    <category>
                        <![CDATA[ storage ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Manish Shivanandhan ]]>
                </dc:creator>
                <pubDate>Mon, 10 Aug 2020 20:25:33 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/08/Screenshot-2020-08-10-at-6.26.31-PM.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>This article will provide an in-depth introduction to AWS S3 — the secure, scalable, and super cheap storage service from Amazon Web Services.</p>
<p>If you have ever worked as a developer, you have likely come across file storage use cases. From simple images to large videos, uploading, storing, and accessing those files when you need them is always tricky.</p>
<p>The usual answer to file storage is to keep them on the same server where you host your web applications. But with the advent of serverless architectures and single-page applications, storing files on the same server is not a good idea.</p>
<p>You could argue that you can store files in databases. But trust me, it won’t be a pleasant experience.</p>
<p>So what's another option?</p>
<h2 id="heading-what-is-s3">What is S3?</h2>
<p>Let's look at AWS S3. S3 is an easy-to-use, scalable, and cheap storage service from Amazon. You can use S3 to store any amount of data for a wide range of use cases.</p>
<p>Static website hosting, data archival, and software delivery are a few general scenarios where S3 would be a perfect tool. </p>
<p>You can easily push and pull data with S3 using the AWS SDK. S3 also supports a number of popular programming languages, so you can use your existing stack and integrate S3 pretty easily.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/Screenshot-2020-08-10-at-6.14.06-PM.png" alt="Image" width="600" height="400" loading="lazy">
<em>AWS Console</em></p>
<p>S3 also offers a great user interface via the <a target="_blank" href="https://aws.amazon.com/console/">AWS console</a>. You can use it to view the data pushed to S3 along with additional options such as security and version control.</p>
<h3 id="heading-buckets">Buckets</h3>
<p>In S3, files are stored in buckets. Buckets are similar to folders on your computer.</p>
<p>Every bucket has its own unique name which can be used only once. For example, if there is a bucket called “freecodecamp”, neither you nor anyone else can re-use the same bucket name.</p>
<p>This is useful to uniquely identify resources and for static website hosting with domain names.</p>
<p>There are no limits on the number of files you can store in a bucket. Buckets also provide additional features such as version control and <a target="_blank" href="https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html">policies</a>.</p>
<p>You can also use different buckets for a single application. For example, an app that stores medical records can use two buckets: one for private customer data and another public bucket that contains whitepapers.</p>
<p>S3 is also an object-based storage service which means S3 considers each file an object. Every object can have its own metadata that includes the name, size, date, and other information.</p>
<h2 id="heading-s3-storage-types">S3 Storage Types</h2>
<p>S3 has three storage classes based on general use cases.</p>
<h3 id="heading-s3-standard">S3 Standard</h3>
<p>S3 Standard is the default storage plan you will be put into when you start using S3. The standard storage class has excellent performance, durability, and availability. </p>
<p>S3 Standard is best if you have data that you have to access frequently.</p>
<h3 id="heading-s3-infrequent-access-s3-ia">S3 Infrequent Access (S3-IA)</h3>
<p>S3 Infrequent Access offers a lower price for data compared to the standard plan. You can use S3-IA for data that you need less often. </p>
<p>S3-IA is great for use cases such as backups and disaster recovery.</p>
<h3 id="heading-glacier">Glacier</h3>
<p>Glacier is the least expensive storage option in S3 but is designed for archival storage. You cannot fetch data from Glacier as fast as Standard or S3-IA, but it is a great option for long term data archival.</p>
<p>In addition to choosing one of these three storage classes, you can also set lifecycle policies in S3. This means that you can schedule files to be moved automatically to S3-IA or Glacier after a certain period of time.</p>
<h2 id="heading-why-use-s3">Why Use S3?</h2>
<p>Companies like Netflix, Dropbox, and Reddit are avid users of S3. The popular file storage system Dropbox built its entire storage capacity on top of Amazon S3. </p>
<p>Let’s look at some of the core features of S3 and understand why it's so popular among enterprises and startups alike.</p>
<h3 id="heading-its-affordable">It's Affordable</h3>
<p>S3 is cheap. I mean super cheap compared to other storage solutions. And with S3, you only pay for what you use. There are no upfront costs, no setup. It's just plug and play.</p>
<p>In addition to affordable pricing, S3 offers a Free tier. This free tier comes with 5GB of storage space, 20,000 GET Requests, 2,000 PUT, COPY, POST, or LIST Requests and 15GB of Data Transfer. The free tier is available every month for the first year.</p>
<p>With S3 you can avoid paying for space or bandwidth you might not even need.</p>
<h3 id="heading-its-scalable">It's Scalable</h3>
<p>S3 scales with your application. Since you pay only for that you use, there is no limit to the data you can store in S3.</p>
<p>This is helpful during multiple scenarios, especially during an unexpected surge in user growth. You don’t have to buy extra space. S3 has you covered.</p>
<h3 id="heading-its-secure">It's Secure</h3>
<p>One of the many reasons companies prefer S3 is its inclination towards security. While you have to secure custom server setups, S3 is secure by default.</p>
<p>This does not mean you cannot store publicly accessible information in S3. S3 locks up all your data with high security unless you explicitly configure not to.</p>
<p>S3 also maintains compliance programs, such as PCI-DSS, HIPAA/HITECH, FedRAMP, EU Data Protection Directive, and FISMA, to help you meet your industry’s regulatory requirements.</p>
<h3 id="heading-it-has-versioning">It Has Versioning</h3>
<p>Versioning means keeping multiple copies of a file and tracking its changes over time. This is useful, especially when you handle sensitive data.</p>
<p>You can also retrieve accidentally deleted files when you enable versioning with S3.</p>
<p>However, if you enable versioning, you are storing multiple copies of the same document. This can have an effect on pricing as well as read/write requests you make. </p>
<p>So just take that into account while integrating versioning for your application.</p>
<p>Versioning is disabled by default for S3 but you can enable versioning using the AWS Console.</p>
<h3 id="heading-its-durable">It's Durable</h3>
<p>Data durability is an underrated feature of S3. Given how common data loss is among companies, data durability is a core factor to consider when building enterprise software.</p>
<p>S3 provides a highly durable storage infrastructure. S3 redundantly stores data in multiple facilities, making you data safe in the event of a system failure. S3 also performs regular data integrity checks to make sure your data is intact.</p>
<p>S3 offers 99.999999999% durability (called the 9s durability) and 99.99% availability of objects over a given year.</p>
<h2 id="heading-s3-use-cases">S3 Use Cases</h2>
<h3 id="heading-static-website-hosting">Static Website Hosting</h3>
<p>You can use S3 as a static website hosting platform. The difference between static and dynamic websites is that dynamic websites receive and process user input. Static websites are used only for displaying information.</p>
<p>With the advent of <a target="_blank" href="https://en.wikipedia.org/wiki/Single-page_application">Single Page Applications</a>, you can host a complete web app on S3, often free of charge. </p>
<p>Frameworks like React and Angular have made user input processing happen within the browser. You can build a SPA that listens to third party APIs and host it within S3. </p>
<p>S3 also has great support for routing, so you can use your own custom domain as well.</p>
<p>I recently wrote an article on hosting a React web app using S3 and <a target="_blank" href="https://medium.com/@manishmshiva/aws-s3-hosting-a-react-web-app-on-aws-s3-2ff2e8ca78dd">you can find the article here</a>.</p>
<h3 id="heading-analytics">Analytics</h3>
<p>You can run queries on your S3 data without moving your data to an analytics platform. This makes S3 a great use case for building powerful analytics applications.</p>
<p>S3 offers multiple options including S3 Select, Amazon Athena, and Amazon Redshift Spectrum. You can also combine these with <a target="_blank" href="https://aws.amazon.com/lambda/">AWS Lambda</a> to perform data processing on the fly.</p>
<h3 id="heading-file-sharing">File Sharing</h3>
<p>Amazon S3 can be also used as a cheap file sharing solution. Like I mentioned earlier in the article, the famous file sharing service Dropbox was first built on top of S3.</p>
<p>With flexible security policies, you can configure your S3 buckets with custom permissions for different customers. S3 also offers <a target="_blank" href="https://aws.amazon.com/s3/transfer-acceleration/#:~:text=S3%20Transfer%20Acceleration%20%28S3TA%29%20reduces,to%20S3%20for%20remote%20applications.">transfer acceleration</a> to speed up large file transfers across longer distances.</p>
<h2 id="heading-summary">Summary</h2>
<p>Amazon S3 is a great tool to work with for your web or mobile application storage requirements. With on-demand pricing and scalability at its core, S3 has been the favored cloud storage solution for small and large businesses alike.</p>
<p>Companies from Netflix to Pinterest trust S3 with their data, thanks to the 99.999999999% data durability promise from Amazon. </p>
<p>You can also use Amazon S3 as a personal storage solution or host your next project via static site hosting. In a nutshell, S3 is a great multi-purpose storage solution catering to a wide range of use cases.</p>
<p><em>I regularly write about Machine Learning, Cyber Security, and AWS. You can signup for my</em> <a target="_blank" href="https://www.manishmshiva.com/"><em>weekly newsletter</em></a> <em>here.</em></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Why You Shouldn’t Use AWS S3 or CloudFront to Deliver Static Assets ]]>
                </title>
                <description>
                    <![CDATA[ By Mehul Mohan AWS is THE cool kid in the town. Every comparison of different cloud providers is incomplete unless you compare them with AWS at least once.  But S3, the most popular solution for storing on the cloud and the one everyone loves, should... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/do-not-use-s3-for-static-assets/</link>
                <guid isPermaLink="false">66d46048246e57ac83a2c7a4</guid>
                
                    <category>
                        <![CDATA[ AWS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ S3 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Mon, 01 Jun 2020 00:20:00 +0000</pubDate>
                <media:content url="https://cdn-media-2.freecodecamp.org/w1280/5f9c9ab0740569d1a4ca271f.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Mehul Mohan</p>
<p>AWS is THE cool kid in the town. Every comparison of different cloud providers is incomplete unless you compare them with AWS at least once. </p>
<p>But S3, the most popular solution for storing on the cloud and the one everyone loves, should not always be your choice. In this article, I'll explain why.</p>
<p><em><strong>Note:</strong> Please don't immediately yell at me about how and why AWS is best. I know they are at the top of cloud computing - and in no way am I trying to target any of their business practices and services.</em>   </p>
<p><em>I've just used CloudFront + S3 myself, along with DigitalOcean + Cloudflare too, and have laid down my observations. Please take my thoughts constructively, and if you think I've made any mistakes, tweet me at <a target="_blank" href="https://twitter.com/mehulmpt">mehulmpt</a>.</em></p>
<h2 id="heading-cloudfront-s3">CloudFront + S3</h2>
<p>CloudFront is another service often used (and recommended) with S3 when you're trying to distribute files digitally all over the globe. CloudFront is a CDN from Amazon with edge servers all over the world. This is how it works:</p>
<p>Your user, say from India, tries to load your website whose server is located in the USA. Let's say you're using a SPA like React or Angular. The first index.html page will load from your origin server (it is usually a good practice to never cache HTML pages, especially if you're using SSR applications to prevent cache mishaps). </p>
<p>After that, if you've hosted your JS/CSS files on CloudFront (S3), those calls will be made to a domain name from CloudFront which resolves to an IP address of a machine closest to your location. In this case, it's probably some server from AWS sitting in some data center in Mumbai, India.</p>
<p>From this point, that server has the responsibility of delivering that file. Two things can happen:</p>
<ul>
<li>your file is already available with that Mumbai server (cached), and that server returns you that file immediately (cache hit), </li>
<li>or it does not has that file and has to perform a trip to your origin server (S3 bucket in this case) to get that file.</li>
</ul>
<p>But even if there's a cache miss, chances are high that it will be still faster for a user compared to not having CloudFront in front. </p>
<p>Why? Because when there is a cache miss and the edge server is trying to reach the main server, it is using a Tier 1 internet connection line operated by Amazon - a trillion-dollar US company. They likely have much better internet connectivity and latency than what your ISP can offer. </p>
<p>Also, because they're on the same global Amazon network, they can do some neat optimizations to save more time.</p>
<p>Alright! Sounds great to me so far, so what's the problem? Hold your horses, we'll get to it.</p>
<h1 id="heading-asset-compression">Asset compression</h1>
<p>CloudFront allows you to deliver compressed assets using GZIP. But there's even a cooler kid in the market: brotli compression. And it is supported by almost every major browser. </p>
<p>Brotli compresses your transmission data even more. This means it's not only good on your wallet, but it's also good for the end-user (because they'll spend less time seeing that loading/white screen).</p>
<p>Amazon CloudFront does not support brotli compression delivery, yet. And I won't blame them for this either. This is because brotli compression is slow to do on the fly (CloudFront does gzip on the fly), so they have not implemented it yet.</p>
<p>Sure, then let's do it ourselves and store it on S3 and deliver the compressed version, right? Unfortunately, it is not as simple, and we'll soon spin down into more of an architecture problem.</p>
<p>A typical asset URL would look like this: http://mysite/assets/javascript/file.js</p>
<p>When your browser makes a request, it sends a header: Accept-Encoding. This header can contain compression algorithms your browser can support, like gzip, deflate, brotli, etc. The server now has to act smart to have maximum efficiency.</p>
<ol>
<li>If the client supports brotli, then always deliver the brotli compressed asset.</li>
<li>If the client supports gzip, then always deliver gzip.</li>
<li>Otherwise, deliver the original file.</li>
<li>Also, make sure that in the response type, the correct Content-Encoding is set so that browser can recognize the compression algorithm.</li>
</ol>
<p>Now, firstly, you have to create 3 variants of every single asset file:</p>
<ol>
<li>file.js</li>
<li>file.js.br - brotli</li>
<li>file.js.gz - gzip</li>
</ol>
<p>And you have to <em>conditionally</em> deliver them depending on if the browser supports it or not. CloudFront is a "dumb" CDN - it will just map your request URL to the file on your server. It cannot perform any transformations unless.... you opt-in for another AWS service - Lambda@edge functions</p>
<p>We all likely know what Lambda is on AWS - you can run functions on the cloud without worrying about underlying infrastructure upscaling or downscaling. Per API request pricing, time-bounded, sweet. Lambda@edge is a similar service but was made for edge servers (CloudFront CDN datacenters)</p>
<p>You can technically configure a Lambda server to act as a "middle man" between the request made by your client and the CloudFront CDN. Lambda can open the request, see the supported content headers, modify the URL accordingly and forward it to the "dumb" CloudFront which is going to retrieve the modified URL file then. </p>
<p>For example, if Lambda sees that browser sent an Accept-Encoding: br then lambda can be used to modify the request URL from /javascript/file.js to /javascript/file.js.br without actually telling the user side. Cloudfront will now retrieve a smaller payload and return a response for a brotli encoding. WIN!</p>
<p>But that is good, isn't it? WHERE is the problem? The problem is... pricing.</p>
<h2 id="heading-aws-is-ridiculously-expensive-for-this-task">AWS is ridiculously expensive (for this task)</h2>
<p>Whatever you've done so far sounds and looks very good. But when you look at what's happening when you start to hit significant numbers, you'll realize that AWS isn't great when it comes to data transfer. <a target="_blank" href="https://www.lastweekinaws.com/blog/why-zoom-chose-oracle-cloud-over-aws-and-maybe-you-should-too/">Zoom just bounced AWS for the same reason</a>. </p>
<p>Plus, with the asset compression, now you also have to pay for Lambda@edge calls. I figured out that implementing Lambda@edge will actually reduce your costs, otherwise you'll pay much more for AWS for traffic!</p>
<p>CloudFront works on data transfer pricing. It does not charge you when it retrieves data from the S3 bucket, it charges you when a user retrieves data from the edge servers.</p>
<h2 id="heading-upper-cost-bound">Upper cost bound</h2>
<p>In the most expensive country - India - CloudFront charges you $0.170 per GB of data transferred. This is huge! </p>
<p>Let's say you have a popular (mainly) Indian website with about 50,000 users visiting your site daily. Also, let's say you make some design changes every week on your site (pretty common for fast iterating products) so you have to invalidate the browser and CloudFront cache.</p>
<p>Also, let's assume on average, a single user downloads about 10MB of the static asset from your site (includes CSS/JS/images/fonts) hosted on S3 proxied through CloudFront.</p>
<p>Let's calculate the cost:</p>
<ol>
<li>50K Indian users</li>
<li>0.17 USD per GB</li>
<li>10MB per user</li>
<li>Every user retrieves this 4 times a month (you flush your cache 4 times - once every week)</li>
</ol>
<p>Cost = 50000 <em> 0.17 </em> (10/1024) <em> 4 = 332 USD. That is your COST of just data transfer! I did not calculate the S3 storage cost and the hosting site cost. (I also did not include lambda pricing because it's not much =&gt; $(0.20 </em> (50,000 * 4))/1 million = 4 cents.)</p>
<h2 id="heading-lower-cost-bound">Lower cost bound</h2>
<p>In this case, let's assume a US based traffic site. The parameters now would be:</p>
<ol>
<li>50K USA users</li>
<li>0.085 USD per GB</li>
<li>3 MB per user</li>
<li>Every user retrieves this 4 times a month (you flush your cache 4 times - once every week)</li>
</ol>
<p>The cost = 50000<em>0.085</em>3*4/1024 = 50 USD. That is the lowest you'll pay when using CloudFront with the mentioned traffic (given that all of your 50K users are from the USA only). And remember, that is the cost only for the data transfers! (Not including server costs for hosting your website.)</p>
<h2 id="heading-alternative">Alternative</h2>
<p>Let's say now you host all these static assets on your main server only - reverse proxied by NGiNX and say, running on a $60 DigitalOcean instance.</p>
<p>Your data transfer per month = 50000 <em> (10/1024) </em> 4 = 1952 GB approximately 2TB - DigitalOcean covers your 1TB of transfer per droplet for free. And it is $10 per 1TB from then, so it'll be $70 net for running the server.</p>
<p>Sure, you'll get some latency now - because you're hosting it yourself (we'll even fix this later). NGiNX is a high performing web server and you can rely on it not to be a bottleneck in your static asset delivery. </p>
<p>So you just dropped the cost of "only asset transfer" from $332 to $70 for running the whole server! Bonus tip? We were focusing on running this only in India, so use a DigitalOcean server from India. This would mean less latency.</p>
<p>Not only this, but you can also opt for Cloudflare CDN too - which is FREE. Cloudflare won't respect your files to keep in the CDN if they're too big or too infrequently accessed. But we're assuming a hell of a popular site here, so we should be fine. If not, opt for any other CDN service, and I guarantee you it will be less than $332 a month.</p>
<p>TL;DR - If you're hosting a website with medium-large amounts of traffic with regularly scheduled updates, it is much more cost efficient to host assets yourself and use external CDNs (or even things like DigitalOcean CDN) instead of using S3 and CloudFront (where data traffic rates are through the roof).</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>I used this setup (CloudFront + AWS S3) on <a target="_blank" href="https://codedamn.com">codedamn.com</a> - a platform for developers to learn and grow. I soon realised that although it looks fancy and I've put codedamn into the big leagues - Amazon - it's just not efficient enough. </p>
<p>Do you agree with me? What do you think? Let me know by <a target="_blank" href="https://twitter.com/mehulmpt">tweeting at me on my Twitter</a> or reaching out to me on <a target="_blank" href="https://instagram.com/mehulmpt">Instagram</a>.</p>
<p>Peace!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Import a Sample Database to your AWS RDS Microsoft SQL Server using S3 ]]>
                </title>
                <description>
                    <![CDATA[ By Clark Jason Ngo This guide was created because it was so hard to find a way to play around with a sample database using AWS RDS MSSQL Server. I hope you find this helpful. If you haven't set up your AWS RDS Microsoft SQL Server and Azure Data Stud... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/cjn-how-to-import-a-sample-database-to-your-aws-rds-microsoft-sql-server-using-s3/</link>
                <guid isPermaLink="false">66d45e0e3dce891ac3a967de</guid>
                
                    <category>
                        <![CDATA[ mssql ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Azure ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Backup ]]>
                    </category>
                
                    <category>
                        <![CDATA[ database ]]>
                    </category>
                
                    <category>
                        <![CDATA[ S3 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Sun, 29 Mar 2020 10:13:35 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/03/Screen-Shot-2020-03-29-at-3.12.23-AM.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Clark Jason Ngo</p>
<p>This guide was created because it was so hard to find a way to play around with a sample database using AWS RDS MSSQL Server. I hope you find this helpful.</p>
<p>If you haven't set up your AWS RDS Microsoft SQL Server and Azure Data Studio, check this guide first: <em><a target="_blank" href="https://www.freecodecamp.org/news/cjn-how-to-connect-your-aws-rds-microsoft-sql-server-using-azure-data-studio/">How to Connect your AWS RDS Microsoft SQL Server using Azure Data Studio</a></em>.</p>
<p>We will be touching the technologies shown below:  </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/image-244.png" alt="Image" width="600" height="400" loading="lazy"></p>
<ul>
<li>Database: AWS RDS Microsoft SQL Server Express Edition</li>
<li>Database tool and GUI: Azure Data Studio</li>
<li>Sample database backup copy: Amazon S3 Bucket</li>
</ul>
<h2 id="heading-adventureworks-sample-database-backup-copy">AdventureWorks sample database backup copy</h2>
<p>To get the OLTP downloads of AdventureWorks, go to this <a target="_blank" href="https://docs.microsoft.com/en-us/sql/samples/adventureworks-install-configure?view=sql-server-ver15">link</a> and choose any sample database. In my example, I choose <code>AdventureWorks2017.bak</code>. We will upload this to the S3 Bucket.</p>
<h2 id="heading-amazon-s3-bucket">Amazon S3 Bucket</h2>
<h3 id="heading-creating-the-s3-bucket">Creating the S3 Bucket</h3>
<ol>
<li>Create a bucket. You can choose any bucket name (example: yourname-sample-dbs).</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/image-202.png" alt="Image" width="600" height="400" loading="lazy"></p>
<ol start="2">
<li>Make sure the region is same as the AWS RDS instance. </li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/image-203.png" alt="Image" width="600" height="400" loading="lazy"></p>
<ol start="3">
<li><p>Tick the following checkboxes:</p>
</li>
<li><p>Block public access to buckets and objects granted through <em>new</em> access control lists (ACLs)</p>
</li>
<li>Block public access and objects granted through <em>any</em> access control lists (ACLs)</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/image-204.png" alt="Image" width="600" height="400" loading="lazy"></p>
<ol start="4">
<li>Access your bucket again by clicking on your created bucket.</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/image-205.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-uploading-the-file-to-the-s3-bucket">Uploading the file to the S3 bucket</h3>
<ol>
<li>Click <strong>Upload</strong>.</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/image-206.png" alt="Image" width="600" height="400" loading="lazy"></p>
<ol start="2">
<li>Choose the database backup file. For example: <code>AdventureWorks2017.bak</code>. Keep choosing <strong>Next</strong> and choose <strong>Upload</strong> at the Review section.</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/image-207.png" alt="Image" width="600" height="400" loading="lazy"></p>
<ol start="3">
<li>Update your Bucket Policy to allow access to your S3 Bucket. Note that your ARN will differ to mine. Hit <strong>Save</strong> afterwards.</li>
</ol>
<pre><code class="lang-json">{
    <span class="hljs-attr">"Version"</span>: <span class="hljs-string">"2012-10-17"</span>,
    <span class="hljs-attr">"Id"</span>: <span class="hljs-string">"Policy1548223592786"</span>,
    <span class="hljs-attr">"Statement"</span>: [
        {
            <span class="hljs-attr">"Sid"</span>: <span class="hljs-string">"Stmt1548223591553"</span>,
            <span class="hljs-attr">"Effect"</span>: <span class="hljs-string">"Allow"</span>,
            <span class="hljs-attr">"Principal"</span>: <span class="hljs-string">"*"</span>,
            <span class="hljs-attr">"Action"</span>: <span class="hljs-string">"s3:GetObject"</span>,
            <span class="hljs-attr">"Resource"</span>: <span class="hljs-string">"arn:aws:s3:::changethis/*"</span>
        }
    ]
}
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/image-208.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-aws-rds-mssql-server-express">AWS RDS - MSSQL Server Express</h2>
<h3 id="heading-creating-an-option-group-for-your-rds-instance">Creating an Option Group for your RDS instance</h3>
<ol>
<li>Click <strong>Option groups</strong>,</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/image-194.png" alt="Image" width="600" height="400" loading="lazy"></p>
<ol start="2">
<li>Create an option group. Choose any name and description. For the Engine, it should match your RDS instance. In my example, I used SQL Server Express Edition so I choose <code>sqlserver-ex</code>.</li>
</ol>
<p>Here are the following Engines and their abbreviations:</p>
<ul>
<li>SQL Server Enterprise Edition: <code>sqlserver-ee</code></li>
<li>SQL Server Standard Edition: <code>sqlserver-se</code></li>
<li>SQL Server Web Edition: <code>sqlserver-web</code></li>
<li>SQL Server Express Edition: <code>sqlserver-ex</code></li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/image-195.png" alt="Image" width="600" height="400" loading="lazy"></p>
<ol start="3">
<li>Once you have created the option group, you'll need to <strong>Add option</strong>.</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/image-199.png" alt="Image" width="600" height="400" loading="lazy"></p>
<ol start="4">
<li>Choose <strong>SQLSERVER_BACKUP_RESTORE</strong> for your Option name. For the IAM role, it is best to create a new role.</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/image-200.png" alt="Image" width="600" height="400" loading="lazy"></p>
<ol start="5">
<li>Choose the S3 bucket where your database file is hosted. For scheduling, choose <strong>Immediately</strong>.</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/image-201.png" alt="Image" width="600" height="400" loading="lazy"></p>
<ol start="6">
<li>Go back to your AWS RDS MSSQL Server instance and click <strong>Modify</strong>.</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/image-196.png" alt="Image" width="600" height="400" loading="lazy"></p>
<ol start="7">
<li>Choose the created option group with <code>sql-server-express-backup</code>, then Click Continue.</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/image-197.png" alt="Image" width="600" height="400" loading="lazy"></p>
<ol start="8">
<li>Choose to <strong>Apply immediately</strong> for scheduling of modifications. </li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/image-198.png" alt="Image" width="600" height="400" loading="lazy"></p>
<ol start="9">
<li>Go back to your AWS RDS MSSQL Server instance page and scroll down and modify <em>Manage IAM Roles</em>. Add the IAM role you have created in S3. For the Feature, choose <strong>S3_INTEGRATION</strong>. </li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/image-210.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/image-211.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-azure-data-studio">Azure Data Studio</h2>
<h3 id="heading-importing-the-sample-database-in-s3-bucket-through-restore-function">Importing the sample database in S3 bucket through restore function</h3>
<ol>
<li>In your connected AWS RDS MSSQL Server, create a new query and type in the following:</li>
</ol>
<pre><code class="lang-sql">exec msdb.dbo.rds_restore_database 
@restore_db_name='AdventureWorks-test', 
@s3_arn_to_restore_from='arn:aws:s3:::clark-sample-dbs/AdventureWorks2017.bak';
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/image-209.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Refresh your Azure Data Studio. Also, try restarting the application if your database did not appear or don't have permission to access it.</p>
<p>Now you are done! Good job! ???</p>
<p>Resources:</p>
<ul>
<li><a target="_blank" href="https://aws.amazon.com/premiumsupport/knowledge-center/native-backup-rds-sql-server/">https://aws.amazon.com/premiumsupport/knowledge-center/native-backup-rds-sql-server/</a></li>
</ul>
<p>Connect with me on LinkedIn <a target="_blank" href="https://www.linkedin.com/in/clarkngo/">here</a></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/image-243.png" alt="Image" width="600" height="400" loading="lazy"></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Point your Domain to an S3 Website Bucket ]]>
                </title>
                <description>
                    <![CDATA[ By Clark Jason Ngo If you're hosting a static website in an S3 bucket and it's your first time buying a domain name, this simple guide is for you. Summary - What You Need Amazon S3 Have an S3 bucket with the same name as your domain name Upload your... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/cjn-how-to-point-your-domain-to-s3-website-bucket/</link>
                <guid isPermaLink="false">66d45e103a8352b6c5a2aa1b</guid>
                
                    <category>
                        <![CDATA[ dns ]]>
                    </category>
                
                    <category>
                        <![CDATA[ S3 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Hosting ]]>
                    </category>
                
                    <category>
                        <![CDATA[ website development, ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Sat, 21 Mar 2020 00:08:34 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/03/domain_name_point_to_s3_bucket.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Clark Jason Ngo</p>
<p>If you're hosting a static website in an S3 bucket and it's your first time buying a domain name, this simple guide is for you.</p>
<h2 id="heading-summary-what-you-need">Summary - What You Need</h2>
<h3 id="heading-amazon-s3">Amazon S3</h3>
<ul>
<li>Have an S3 bucket with the same name as your domain name</li>
<li>Upload your website's code</li>
<li>Allow public access</li>
<li>Add a policy to enable S3 GetObject</li>
<li>Enable static website hosting</li>
</ul>
<h3 id="heading-domain-name-provider">Domain Name provider</h3>
<ul>
<li>In your domain name's DNS Zone settings, delete all <strong>A</strong> records</li>
<li>In DNS Zone settings, add <em>www</em> to <strong>subdomain</strong> and the S3 endpoint in hostname for <strong>CNAME</strong> records</li>
</ul>
<p>Let's go through these steps one by one.</p>
<h2 id="heading-step-1-create-an-s3-bucket">Step 1: Create an S3 bucket</h2>
<p>Create an S3 bucket to host your files for your website</p>
<p>First you need to create a bucket for your website. The name for your bucket must be the same as your domain name. Let's say we bought the domain name <strong>www.clarkngo.net</strong>. Then my S3 bucket's name should be <strong>www.clarkngo.net</strong> as well. </p>
<p>After configuration, my endpoint should look similar to this:</p>
<p>http://www.clarkngo.net.s3-website-us-west-2.amazonaws.com</p>
<p>Go to your AWS console and login. Choose S3.</p>
<ol>
<li>Click <strong>Buckets</strong></li>
<li>Click <strong>Create bucket</strong></li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/image-119.png" alt="Image" width="600" height="400" loading="lazy"></p>
<ol start="3">
<li><p>Add your domain name in the <strong>bucket name</strong></p>
</li>
<li><p>You may choose any <strong>Region</strong></p>
</li>
</ol>
<h3 id="heading-creating-the-s3-bucket-and-general-configuration">Creating the S3 bucket and general configuration</h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/image-118.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Follow the checkboxes below and click <strong>Create Bucket</strong>.</p>
<p>Only tick the following:</p>
<ul>
<li><strong>Block public access to bucket and objects granted through <em>new</em> access control lists (ACLs)</strong></li>
<li><strong>Block public access to bucket and objects granted through <em>any</em> access control lists (ACLs)</strong></li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/image-120.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-uploading-files-to-the-s3-bucket">Uploading files to the S3 Bucket</h3>
<ol>
<li>Click <strong>Overview</strong> and <strong>Upload</strong>.</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/image-121.png" alt="Image" width="600" height="400" loading="lazy"></p>
<ol start="2">
<li>Upload your website files in <strong>Select Files</strong></li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/image-122.png" alt="Image" width="600" height="400" loading="lazy"></p>
<ol start="3">
<li><p>For <strong>Set permissions</strong>, hit <strong>Next</strong>.</p>
</li>
<li><p>For <strong>Set properties</strong>, hit <strong>Next</strong>. (The default is Standard S3.)</p>
</li>
<li><p>For <strong>Review</strong>, hit <strong>Upload</strong>.</p>
</li>
</ol>
<h3 id="heading-editing-the-bucket-policy">Editing the Bucket Policy</h3>
<ol>
<li>Click <strong>Permissions</strong>, then <strong>Bucket Policy</strong>.</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/image-123.png" alt="Image" width="600" height="400" loading="lazy"></p>
<ol start="2">
<li>Add the policy. (Note: For your website you'll change <strong>arn:aws::s3:::www.clarkngo.net/*</strong>)</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/image-124.png" alt="Image" width="600" height="400" loading="lazy"></p>
<pre><code>{
    <span class="hljs-string">"Version"</span>: <span class="hljs-string">"2012-10-17"</span>,
    <span class="hljs-string">"Id"</span>: <span class="hljs-string">"Policy1548223592786"</span>,
    <span class="hljs-string">"Statement"</span>: [
        {
            <span class="hljs-string">"Sid"</span>: <span class="hljs-string">"Stmt1548223591553"</span>,
            <span class="hljs-string">"Effect"</span>: <span class="hljs-string">"Allow"</span>,
            <span class="hljs-string">"Principal"</span>: <span class="hljs-string">"*"</span>,
            <span class="hljs-string">"Action"</span>: <span class="hljs-string">"s3:GetObject"</span>,
            <span class="hljs-string">"Resource"</span>: <span class="hljs-string">"arn:aws:s3:::www.clarkngo.net/*"</span>
        }
    ]
}
</code></pre><ol start="3">
<li>Hit <strong>Save</strong>.</li>
</ol>
<h3 id="heading-static-website-hosting">Static website hosting</h3>
<ol>
<li>Click <strong>Properties</strong>, then <strong>Static website hosting</strong>.</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/image-125.png" alt="Image" width="600" height="400" loading="lazy"></p>
<ol start="2">
<li><p>Choose <strong>Use this bucket to host a website</strong>.</p>
</li>
<li><p>For Index document, type <em>index.html</em>.</p>
</li>
<li><p>For Error document, type <em>index.html</em>.</p>
</li>
<li><p>Hit <strong>Save</strong>.</p>
</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/image-126.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-step-2-add-the-s3-endpoint-to-your-domain">Step 2: Add the S3 Endpoint to your Domain</h2>
<h3 id="heading-editing-your-dns-zone">Editing your DNS Zone</h3>
<ol>
<li>Login to your domain provider.</li>
<li>In this example, choose <strong>Name Servers/DNS</strong>, then <strong>Modify DNS Zone</strong> (or the equivalent).</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/image-127.png" alt="Image" width="600" height="400" loading="lazy"></p>
<ol start="3">
<li>Remove all <strong>A</strong> records in your domain. Usually it will have a default IP address for a 404 Not Found page.</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/image-128.png" alt="Image" width="600" height="400" loading="lazy"></p>
<ol start="4">
<li><p>Add a <strong>CNAME</strong> to point to the S3 Bucket:</p>
</li>
<li><p>add <strong>www</strong> for the Subdomain.</p>
</li>
<li>add <strong>www.clarkngo.net.s3-website-us-west-2.amazonaws.com</strong> (the S3 Endpoint) to the Hostname.</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/image-129.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>And you're done! Note that it might take a while for your new settings take effect.</p>
<p>Connect with me in LinkedIn <a target="_blank" href="https://www.linkedin.com/in/clarkngo/">here</a>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/image-133.png" alt="Image" width="600" height="400" loading="lazy"></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to host and deploy a static website or JAMstack app to AWS S3 and CloudFront ]]>
                </title>
                <description>
                    <![CDATA[ S3 and CloudFront are AWS cloud services that make serving static assets powerful and cheap. How can we host a simple static website or JAMstack app on it? A little about AWS What are the benefits of serving from S3 and CloudFront? Before we start, ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-host-and-deploy-a-static-website-or-jamstack-app-to-s3-and-cloudfront/</link>
                <guid isPermaLink="false">66bee8fdf53892da32acd273</guid>
                
                    <category>
                        <![CDATA[ AWS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ beginners guide ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Cloud ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Cloud Services ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Cloud Solutions ]]>
                    </category>
                
                    <category>
                        <![CDATA[ cloudfront ]]>
                    </category>
                
                    <category>
                        <![CDATA[ HTML ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JAMstack ]]>
                    </category>
                
                    <category>
                        <![CDATA[ General Programming ]]>
                    </category>
                
                    <category>
                        <![CDATA[ S3 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Static Site Generators ]]>
                    </category>
                
                    <category>
                        <![CDATA[ tech  ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Tutorial ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Colby Fayock ]]>
                </dc:creator>
                <pubDate>Wed, 11 Mar 2020 13:16:31 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/03/static-website-in-aws-s3.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>S3 and CloudFront are AWS cloud services that make serving static assets powerful and cheap. How can we host a simple static website or JAMstack app on it?</p>
<ul>
<li><a class="post-section-overview" href="#heading-a-little-about-aws">A little about AWS</a></li>
<li><a class="post-section-overview" href="#heading-what-are-the-benefits-of-serving-from-s3-and-cloudfront">What are the benefits of serving from S3 and CloudFront?</a></li>
<li><a class="post-section-overview" href="#heading-before-we-start-youll-need-an-aws-account">Before we start, you’ll need an AWS account</a></li>
<li><a class="post-section-overview" href="#heading-storing-your-website-on-s3">Storing your website on S3</a></li>
<li><a class="post-section-overview" href="#heading-serving-your-website-on-s3">Serving your website on S3</a></li>
<li><a class="post-section-overview" href="#heading-distributing-your-website-on-cloudfront">Distributing your website on CloudFront</a></li>
<li><a class="post-section-overview" href="#heading-custom-domain-names">Custom domain names</a></li>
<li><a class="post-section-overview" href="#heading-advanced-aws-usage">Advanced AWS Usage</a></li>
<li><a class="post-section-overview" href="#heading-resources">Resources</a></li>
</ul>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/1lDGDzmbQWg" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<h2 id="heading-a-little-about-aws">A little about AWS</h2>
<p>If you’re not familiar, <a target="_blank" href="https://aws.amazon.com/">AWS</a> (Amazon Web Services) is a cloud service provider that gives developers opportunities to build pretty much anything they can imagine in the cloud.</p>
<p>Though their <a target="_blank" href="https://aws.amazon.com/products/">services</a> extend beyond the likes of <a target="_blank" href="https://aws.amazon.com/machine-learning/">machine learning</a> and <a target="_blank" href="https://aws.amazon.com/ai/">artificial intelligence</a>, we’re going to stick with the entry level services for the purpose of this guide that will allow us to easily host an HTML website.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/aws-services-overview.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Types of AWS services available</em></p>
<p>Building a site with S3 and CloudFront is a common recipe that small and high scale companies across the web use, but let’s break down what each service actually does.</p>
<h3 id="heading-object-storage-with-s3">Object storage with S3</h3>
<p><a target="_blank" href="https://aws.amazon.com/s3/">S3</a> (Simple Storage Service) acts as your hosting for your static website. Think of it like a hard drive in the cloud which we’re not able to use it for processing purposes, but rather for simple file storage and access.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/aws-s3-bucket-file-list.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>List of files from a static site in an AWS S3 bucket</em></p>
<p>When an app or website is compiled in static form, this is all we need to serve it to the people visiting our site. The HTML is sent in the initial request “as is” (unless there’s processing with your provider) and any additional work occurs after the page loads in the browser usually by JavaScript. This allows us to take this simple (and cheap) approach by serving these files from S3.</p>
<h3 id="heading-content-delivery-network-with-cloudfront">Content Delivery Network with CloudFront</h3>
<p><a target="_blank" href="https://aws.amazon.com/cloudfront/">CloudFront</a> works as a <a target="_blank" href="https://en.wikipedia.org/wiki/Content_delivery_network">CDN</a> (Content Delivery Network) that sits in front of your website, caching the files, and serving them directly to the people visiting your site.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/cdn-distribution-map.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>CDN Diagram</em></p>
<p>Where you host and serve your website from, typically called the origin, is the main source of your files and can serve the website itself. But putting a CDN in front of it provides the people accessing your content a shorter and faster way to make their request.</p>
<h2 id="heading-what-are-the-benefits-of-serving-from-s3-and-cloudfront">What are the benefits of serving from S3 and CloudFront?</h2>
<p>Given the rise in the <a target="_blank" href="https://jamstack.org/">JAMstack</a> era, many services are popping up that provide similar services for static sites that make it really easy to deploy. Some even come with a generous free tier like <a target="_blank" href="https://www.netlify.com/">Netlify</a> and <a target="_blank" href="https://zeit.co/">Zeit</a>!</p>
<p>But sometimes developers need a little bit more control over their services or they need to integrate into a larger cloud pipeline that’s already 99% percent in AWS, which is exactly where S3 shines. Also, chances are, during your first year you might still qualify for AWS’s <a target="_blank" href="https://aws.amazon.com/free/">free tier</a>.</p>
<h3 id="heading-fitting-in-to-the-aws-well-architected-framework">Fitting in to the AWS Well-Architected Framework</h3>
<p>As a lead provider in cloud services, AWS has published many guides to help developers and teams strive for excellence in their solutions in terms of performance, cost, and security.</p>
<p>One particular guideline is their 5 pillars of what they describe as a <a target="_blank" href="https://aws.amazon.com/architecture/well-architected/">“well-architected" infrastructure</a>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/aws-well-architected-framework.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>AWS Well-Architected Framework</em></p>
<p>By default, we check all of these boxes with our hosting solution by using S3 and CloudFront. Out of the box, the HTML and assets you serve will be fast, cheap, secure, and reliable.</p>
<h3 id="heading-the-beauty-of-static-and-jamstack-sites">The beauty of static and JAMstack sites</h3>
<p>Building on top of the pillars, what you’re actually serving is a static HTML file and group of assets that won’t require any type of rendering resources on the initial request. Before this, a common problem was having to worry about a site crashing due to heavy load. But with S3 and CloudFront, your website is infinitely scalable.</p>
<p>On a similar note, when that server scales up as it's trying to serve millions of hits on your post that went viral, so will your costs. Serving a static site is cheap and can greatly reduce the cost associated with running a web server.</p>
<h2 id="heading-before-we-start-youll-need-an-aws-account">Before we start, you’ll need an AWS account</h2>
<p>To work through this guide, you’ll need an AWS account. Luckily, it's free to create an account – you’ll only pay for the services used.</p>
<p>On top of that, AWS provides a generous free tier for some of its services. Some services provide only 12 months of a free tier (like S3) where others are always eligible for the free tier (like <a target="_blank" href="https://aws.amazon.com/lambda/">Lambda</a>), so make sure to do your homework so you don’t rack up an unexpectedly high bill.</p>
<p>To create your account, head over to the AWS website and then continue on to get started: <a target="_blank" href="https://aws.amazon.com/">https://aws.amazon.com/</a>.</p>
<h2 id="heading-storing-your-website-on-s3">Storing your website on S3</h2>
<p>To get started, we’re going to begin with a simple HTML file that will serve as our website. This will allow us to focus more on the process of hosting rather than the intricacies of the website itself.</p>
<h3 id="heading-creating-our-website-file">Creating our website file</h3>
<p>Begin by creating a new folder called <code>my-static-site</code>. Inside that folder, let's create a new file called <code>index.html</code> and add the following to the file:</p>
<pre><code class="lang-html"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">“en”</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">“UTF-8”</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">“viewport”</span> <span class="hljs-attr">content</span>=<span class="hljs-string">“width</span>=<span class="hljs-string">device-width,</span> <span class="hljs-attr">initial-scale</span>=<span class="hljs-string">1.0”</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>My Static Website<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Hello World!<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>This is my static website. ?<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>If you open this file from your computer in your favorite browser, you should now be seeing this.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/hello-world-local-website-file-1.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Hello World! Opening a local webpage</em></p>
<h3 id="heading-creating-a-new-bucket">Creating a new bucket</h3>
<p>Head on over to your AWS account, log in, and navigate to your <a target="_blank" href="https://s3.console.aws.amazon.com/s3/">S3 console</a>.</p>
<p>Once there, let’s create our bucket by clicking on the blue <strong>Create bucket</strong> button:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/aws-s3-create-bucket.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Creating a bucket in AWS S3</em></p>
<p>The first thing AWS wants us to do is enter a <strong>Bucket name</strong>. The bucket name must be globally unique, meaning, the name you use can be the only one in the world, so let’s try something like <code>[yourname]-static-website</code>, where I’ll use <code>colbyfayock-static-website</code>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/aws-s3-bucket-name.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Naming a bucket in AWS S3</em></p>
<p>Next, let’s set the <a target="_blank" href="https://aws.amazon.com/about-aws/global-infrastructure/regions_az/"><strong>Region</strong></a>. This is the geographic location where AWS will host the bucket and your website. You’re probably fine with the default, but if you’d like, you can select the location closest to you if it’s permitted. Since I’m in Virginia, I’m going to stick with my default of <strong>US East (N. Virginia)</strong>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/aws-s3-bucket-region.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Setting the region of a bucket in AWS S3</em></p>
<p>Finally, hit the <strong>Create</strong> button on the bottom left of the page.</p>
<p><em>Note: even if you use the <code>[yourname]-static-website</code> pattern, there’s a chance the name will be taken. If it’s taken, AWS will show an error stating “Bucket name already exists,” at which point you’ll want to try a new name of your choosing.</em></p>
<p>Alternatively, you can hit <strong>Next</strong> for advanced usage, but for this guide, we’re okay with all of the defaults S3 provides.</p>
<p>If successful, you should now see your bucket in the list on the S3 console dashboard.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/aws-s3-bucket.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>New bucket in AWS S3</em></p>
<h3 id="heading-uploading-your-website-to-the-bucket">Uploading your website to the bucket</h3>
<p>Let’s navigate to our new bucket by clicking the row of our bucket. You’ll be greeted with a message stating “This bucket is empty. Upload new objects to get started,” so that’s what we’ll do.</p>
<p>Click the <strong>Upload</strong> button to get started.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/aws-s3-bucket-upload.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Uploading files to AWS S3</em></p>
<p>You’ll then see a popup that will ask you to upload a file. Click on the <strong>Add files</strong> button and select your <code>index.html</code> file we created earlier.</p>
<p>Once selected, click the <strong>Upload</strong> button on the bottom left.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/aws-s3-bucket-upload-files.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Selecting files to upload in AWS S3</em></p>
<p>And now your file is uploaded to S3!</p>
<h2 id="heading-serving-your-website-on-s3">Serving your website on S3</h2>
<p>If you try to navigate to your <code>index.html</code> file and open it, you’ll notice a big ugly "Access Denied" message.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/aws-s3-access-denied.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Access Denied to bucket file</em></p>
<p>This is because your file doesn’t currently have the permissions and settings necessary to serve the file to the public, so let’s fix that.</p>
<h3 id="heading-setting-up-your-bucket-as-a-website">Setting up your bucket as a website</h3>
<p>Navigate to the <strong>Properties</strong> tab inside of your bucket, then click <strong>Static website hosting</strong>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/aws-s3-properties-static-hosting.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Setting up an AWS S3 bucket for statice website hosting</em></p>
<p>Once there, we want to do a few things:</p>
<ul>
<li>Note down the <strong>Endpoint</strong> at the top of the block. We’ll use this to access our site later (you can always find this here again)</li>
<li>Select the “Use this bucket to host a website” option</li>
<li>Enter <code>index.html</code> in the <strong>Index document</strong> field</li>
<li>Finally hit <strong>Save</strong></li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/aws-s3-static-website-configuration.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Configuring an AWS S3 bucket for static website hosting</em></p>
<h3 id="heading-setting-up-your-bucket-policy-and-permissions">Setting up your bucket policy and permissions</h3>
<p>Next, navigate to the <strong>Permissions</strong> tab. Here we’ll want to do 2 things: unblock all public access and add a Bucket Policy.</p>
<p>First, on the main page, let’s click <strong>Edit</strong> to unblock all access.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/aws-s3-bucket-permissions.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Configuring an AWS S3 bucket permissions</em></p>
<p>Then, uncheck the “Block all public access” checkbox and hit <strong>Save</strong>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/aws-s3-bucket-block-access.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Allowing public access to an AWS S3 bucket</em></p>
<p>AWS will ask you to confirm these settings, as this may not always be what you want to do with your bucket. But for the purposes of hosting a website, we want the whole world to see, so type in the word “confirm” and hit the <strong>Confirm</strong> button.</p>
<p>After confirming, click the <strong>Bucket policy</strong> button and you’ll be taken to a text editor.</p>
<p>In this text box, we’ll want to paste the following snippet. Within this snippet, make sure to replace <code>[your-bucket-name]</code> with the name of your bucket, otherwise you will not be able to save this file.</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"Version"</span>:<span class="hljs-string">"2012-10-17"</span>,
  <span class="hljs-attr">"Statement"</span>:[{
    <span class="hljs-attr">"Sid"</span>:<span class="hljs-string">"PublicReadGetObject"</span>,
        <span class="hljs-attr">"Effect"</span>:<span class="hljs-string">"Allow"</span>,
      <span class="hljs-attr">"Principal"</span>: <span class="hljs-string">"*"</span>,
      <span class="hljs-attr">"Action"</span>:[<span class="hljs-string">"s3:GetObject"</span>],
      <span class="hljs-attr">"Resource"</span>:[<span class="hljs-string">"arn:aws:s3:::[your-bucket-name]/*”
      ]
    }
  ]
}</span>
</code></pre>
<p><a target="_blank" href="https://docs.aws.amazon.com/AmazonS3/latest/dev/WebsiteAccessPermissionsReqd.html#bucket-policy-static-site">This policy</a> states that it’s allowing the public to perform a GetObject request on the S3 resource, which is your S3 bucket.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/aws-s3-static-website-bucket-policy.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Setting up a public policy for an AWS S3 bucket</em></p>
<p>After you add the policy, click the <strong>Save</strong> button. Your should now see a message stating "This bucket has public access.”</p>
<h3 id="heading-previewing-your-new-bucket-website">Previewing your new bucket website</h3>
<p>If you noted down the Endpoint from your Properties page, you can now visit that address to see your website. The endpoint should look like this:</p>
<pre><code class="lang-plaintext">http://[your-bucket-name].s3-website-[region-id].amazonaws.com
</code></pre>
<p>If you didn’t, jump back up a few steps to remind yourself how to find it or look under the Properties tab.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/aws-s3-static-website.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Hello World! Opening an AWS S3 website</em></p>
<p>Congrats, you're halfway there! ?</p>
<h2 id="heading-distributing-your-website-on-cloudfront">Distributing your website on CloudFront</h2>
<p>Now that we have our static website being served from a bucket on S3, let’s take it up another level and serve it across the world using CloudFront.</p>
<h3 id="heading-creating-a-cloudfront-distribution">Creating a CloudFront distribution</h3>
<p>Navigate to your <a target="_blank" href="https://console.aws.amazon.com/cloudfront">CloudFront dashboard</a> and click the <strong>Create Distribution</strong> button.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/aws-cloudfront-create-distribution.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Creating a new distribution in AWS CloudFront</em></p>
<p>Next, select <strong>Get Started</strong> under the <strong>Web</strong> delivery method.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/aws-cloudfront-creating-web-distribution.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Getting started with an AWS CloudFront distribution with Web delivery</em></p>
<p>Here, we’ll enter a few custom parameters to get our distribution set up.</p>
<p>Click into the <strong>Origin Domain Name</strong> field. Once selected, a dropdown list should appear where you can select the S3 bucket you just created. Go ahead and select your S3 bucket.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/aws-cloudfront-distribution-origin-name-1.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Setting the origin domain name in AWS CloudFront to your bucket</em></p>
<p>While you can <a target="_blank" href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/distribution-web-values-specify.html">customize most of the settings</a> to your liking, for our purposes, we’re going to leave all as their default values except for one.</p>
<p>Scroll down to the <strong>Default Root Object</strong> field and type <code>index.html</code>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/aws-cloudfront-distribution-default-root-object-1.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Setting the Default Root Object for a distribution in AWS CloudFront</em></p>
<p>After, scroll down to the bottom and click <strong>Create Distribution</strong> in the bottom right.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/aws-cloudfront-setup-create-1.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Creating an AWS CloudFront distribution</em></p>
<h3 id="heading-previewing-your-new-cloudfront-distribution">Previewing your new CloudFront distribution</h3>
<p>After hitting the <strong>Create</strong> button, it will take some time for your distribution to be created and set up. You’ll notice on the <strong>CloudFront Distributions</strong> list page that the <strong>Status</strong> of your new distribution is <strong>In Progress</strong>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/aws-cloudfront-distribution-in-progress-1.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>AWS CloudFront distribution deployment is In Progress</em></p>
<p>Once this completes, it will say <strong>Deployed</strong>. Then you can find your <strong>Domain Name</strong> in the same row.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/aws-cloudfront-distribution-deployed.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>AWS CloudFront distribution is Deployed</em></p>
<p>Using the value in the Domain Name column, open your distribution in your browser and success! You now are viewing your S3 bucket through CloudFront’s distribution network.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/aws-cloudfront-static-website-1.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Hello World! Opening an AWS CloudFront website</em></p>
<h2 id="heading-custom-domain-names">Custom domain names</h2>
<p>While most of us will probably want to use a custom domain name with our website, we’re not going to dive too deep into that this guide, as there are many ways to set that up depending on where you purchase your domain name.</p>
<p>However, here are a few things to consider.</p>
<h3 id="heading-https-ssl-certificate">HTTPS / SSL Certificate</h3>
<p>If you’re creating your CloudFront distribution to use with a custom domain name, you'll most likely want to configure your distribution with an <a target="_blank" href="https://www.cloudflare.com/learning/ssl/what-is-an-ssl-certificate/">SSL certificate</a> using AWS’s <a target="_blank" href="https://aws.amazon.com/certificate-manager/">Certificate Manager</a>. Alternatively you can provide your own certificate with tools like <a target="_blank" href="https://letsencrypt.org/">Let's Encrypt</a>, but by using ACM, AWS makes it easy to pull in the records for use with your distribution.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/fay.io-ssl-certificate.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Once in ACM, you’ll want to configure the certificate, map what domains and subdomains should match (typically <code>*.domain.com</code>), and then create your certificate to use with your distribution.</p>
<p>To get started, you can check out the AWS guide for <a target="_blank" href="https://docs.aws.amazon.com/acm/latest/userguide/gs-acm-request-public.html">requesting a public certificate</a>.</p>
<h3 id="heading-cnames-and-aliases">CNAMEs and Aliases</h3>
<p>A common approach to setting up a custom domain is to use a CNAME. CloudFront makes this pretty painless, as you’ll add it as a configuration option when you’re configuring your distribution.</p>
<p>To get started with setting up a CNAME in CloudFront, <a target="_blank" href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/CNAMEs.html">see the AWS guide</a>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/fay.io-route53-alias.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>If you’re using <a target="_blank" href="https://aws.amazon.com/route53/">Route53</a> to manage your <a target="_blank" href="https://www.cloudflare.com/learning/dns/what-is-dns/">DNS</a>, you can then set up an A record (alias) to point to your distribution. You can learn more <a target="_blank" href="https://aws.amazon.com/premiumsupport/knowledge-center/route-53-create-alias-records/">using this guide</a>.</p>
<h2 id="heading-advanced-aws-usage">Advanced AWS Usage</h2>
<p>For this guide, we walked you through setting up a new static website and app using the AWS console. But whether you want to learn more, improve your deploy efficiency, or want to automate this process, you’ll want to take a it a step further with the AWS CLI or CloudFormation.</p>
<p>While we won’t walk you through how to use these tools here, we’ll get you started with a little bit of an idea of what you’re up against.</p>
<h3 id="heading-aws-cli">AWS CLI</h3>
<p>The <a target="_blank" href="https://aws.amazon.com/cli/">AWS CLI</a> allows someone to perform AWS operations from the command line. This can be incredibly powerful when you want to script out your resource creation or if you simply prefer to do all of your work from the terminal.</p>
<p>Once set up locally, you’ll be able to perform actions like creating a bucket using the following command:</p>
<pre><code class="lang-shell">aws s3api create-bucket —-bucket [your-bucket-name] —-region [bucket-region]
</code></pre>
<p>To get started, check out the AWS CLI <a target="_blank" href="https://github.com/aws/aws-cli">Github page</a> or the AWS CLI <a target="_blank" href="https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-welcome.html">User Guide</a>    .</p>
<h3 id="heading-aws-cloudformation">AWS CloudFormation</h3>
<p>AWS preaches “infrastructure as code.” It’s the idea that you can spin up your infrastructure using something that’s written in a file, where in this particular case, it would be a CloudFormation template. This allows you to have a repeatable process that will be the same each time you perform the deploy.</p>
<p><a target="_blank" href="https://aws.amazon.com/cloudformation/">CloudFormation</a> allows you to set up a configuration file that will deploy the services and resources of your choosing by pointing to that file with the CLI or by uploading it in the console.</p>
<p>Here’s an <a target="_blank" href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/quickref-s3.html#scenario-s3-bucket-website">example from AWS</a> of what that looks like for a static S3 bucket that could serve as a website.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/aws-cloudformation-template-s3.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>AWS CloudFront template example</em></p>
<p>To get started, check out AWS’s CloudFormation <a target="_blank" href="https://aws.amazon.com/cloudformation/resources/templates/">example templates</a> or their <a target="_blank" href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/GettingStarted.Walkthrough.html">Get Started guide</a>.</p>
<h2 id="heading-resources">Resources</h2>
<p>If you’re interested in getting deeper into the AWS ecosystem, here are a few resources to get started:</p>
<ul>
<li><a target="_blank" href="https://www.freecodecamp.org/news/aws-certified-cloud-practitioner-training-2019-free-video-course/">AWS Certified Cloud Practitioner Training 2019 - A Free 4-hour Video Course</a> (freeCodeCamp.org)</li>
<li><a target="_blank" href="https://www.freecodecamp.org/news/awscertified-challenge-free-path-aws-cloud-certifications/">Introducing The #AWSCertified Challenge: A Path to Your First AWS Certifications</a> (freeCodeCamp.org)</li>
<li><a target="_blank" href="https://aws.amazon.com/getting-started/tutorials/">10-Minute Tutorials</a> (AWS)</li>
<li><a target="_blank" href="https://acloud.guru/">A Cloud Guru</a> (Paid courses)</li>
<li><a target="_blank" href="https://aws.amazon.com/solutions/case-studies/">AWS Case Studies</a> (AWS)</li>
</ul>
<div id="colbyfayock-author-card">
  <p>
    <a href="https://twitter.com/colbyfayock">
      <img src="https://res.cloudinary.com/fay/image/upload/w_2000,h_400,c_fill,q_auto,f_auto/w_1020,c_fit,co_rgb:007079,g_north_west,x_635,y_70,l_text:Source%20Sans%20Pro_64_line_spacing_-10_bold:Colby%20Fayock/w_1020,c_fit,co_rgb:383f43,g_west,x_635,y_6,l_text:Source%20Sans%20Pro_44_line_spacing_0_normal:Follow%20me%20for%20more%20JavaScript%252c%20UX%252c%20and%20other%20interesting%20things!/w_1020,c_fit,co_rgb:007079,g_south_west,x_635,y_70,l_text:Source%20Sans%20Pro_40_line_spacing_-10_semibold:colbyfayock.com/w_300,c_fit,co_rgb:7c848a,g_north_west,x_1725,y_68,l_text:Source%20Sans%20Pro_40_line_spacing_-10_normal:colbyfayock/w_300,c_fit,co_rgb:7c848a,g_north_west,x_1725,y_145,l_text:Source%20Sans%20Pro_40_line_spacing_-10_normal:colbyfayock/w_300,c_fit,co_rgb:7c848a,g_north_west,x_1725,y_222,l_text:Source%20Sans%20Pro_40_line_spacing_-10_normal:colbyfayock/w_300,c_fit,co_rgb:7c848a,g_north_west,x_1725,y_295,l_text:Source%20Sans%20Pro_40_line_spacing_-10_normal:colbyfayock/v1/social-footer-card" alt="Follow me for more Javascript, UX, and other interesting things!" width="2000" height="400" loading="lazy">
    </a>
  </p>
  <ul>
    <li>
      <a href="https://twitter.com/colbyfayock">? Follow Me On Twitter</a>
    </li>
    <li>
      <a href="https://youtube.com/colbyfayock">?️ Subscribe To My Youtube</a>
    </li>
    <li>
      <a href="https://www.colbyfayock.com/newsletter/">✉️ Sign Up For My Newsletter</a>
    </li>
  </ul>
</div>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Simple site hosting with Amazon S3 and HTTPS ]]>
                </title>
                <description>
                    <![CDATA[ By Georgia Nola Hiya folks! In this tutorial I’ll show you how to host a static website with HTTPS on AWS with a custom domain. All this is possible using AWS free tier. However, the services we are going to use do incur some small charges. Generally... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/simple-site-hosting-with-amazon-s3-and-https-5e78017f482a/</link>
                <guid isPermaLink="false">66c35edca365c359945c9b64</guid>
                
                    <category>
                        <![CDATA[ AWS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Cloud Services ]]>
                    </category>
                
                    <category>
                        <![CDATA[ S3 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Hosting ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Tue, 08 Jan 2019 16:25:45 +0000</pubDate>
                <media:content url="https://cdn-media-1.freecodecamp.org/images/1*nKAE02IQZHWQ9oqNgGX3ag.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Georgia Nola</p>
<p>Hiya folks!</p>
<p>In this tutorial I’ll show you how to host a static website with HTTPS on AWS with a custom domain. All this is possible using AWS free tier.</p>
<p>However, the services we are going to use do incur some small charges. Generally speaking these shouldn’t exceed $1/month.</p>
<p>We’ll be using a combination of the following AWS services:<br> —S3<br> — Route53<br> — Certificate manager<br>— CloudFront</p>
<p><em>Let’s get into it!</em></p>
<h3 id="heading-setup-your-s3-buckets">Setup your S3 buckets</h3>
<p>First, you’ll need <strong>two S3 buckets</strong>, both should match your custom domain name with the second including the www subdomain.</p>
<p>Bucket 1: mywebsite.com<br>Bucket 2: www.mywebsite.com</p>
<p>The first bucket (mywebsite.com) is the main bucket for your site. This contains all your files and assets for your static website.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/8tMXguNd0mEM-Kdt54Dy0WzMNvD0h0D0Moci" alt="Image" width="564" height="556" loading="lazy"></p>
<p>Next we setup this bucket for static site hosting. You can find this under the Properties tab of the bucket, and we’re going to keep the defaults provided here with the index of the site set to index.html.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/-HHilv-8c1Y3OHdtaZhJR9DNlphJOBFd87gy" alt="Image" width="800" height="506" loading="lazy"></p>
<p>We also need to make this bucket publicly accessible as a user’s browser will need to access the bucket’s files in order to render the website. We can do this by setting a Bucket Policy under the Permissions tab.</p>
<pre><code>{       <span class="hljs-string">"Version"</span>: <span class="hljs-string">"2012-10-17"</span>,       <span class="hljs-string">"Statement"</span>: [        {            <span class="hljs-string">"Sid"</span>: <span class="hljs-string">"PublicReadGetObject"</span>,            <span class="hljs-string">"Effect"</span>: <span class="hljs-string">"Allow"</span>,            <span class="hljs-string">"Principal"</span>: <span class="hljs-string">"*"</span>,            <span class="hljs-string">"Action"</span>: <span class="hljs-string">"s3:GetObject"</span>,            <span class="hljs-string">"Resource"</span>: <span class="hljs-string">"MY_BUCKET_ARN"</span>        }    ]}
</code></pre><p>This is a simple policy that will only allow public read access of objects in the bucket. Now, if you head to the endpoint defined in the static hosting config of the bucket, you should see your website.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/yEcjdf6UEr8iPVBjQCT0CtidDLpUQyhQCbLG" alt="Image" width="800" height="475" loading="lazy"></p>
<p>Progress! But we can do better than that.</p>
<p>The second bucket (www.mywebsite.com) we will leave empty but configure to redirect to our first bucket using HTTP as the protocol (we’ll make it HTTPS later).</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/MphGJGErSalxmf76wjQbOGSuyhg6y50dPWxT" alt="Image" width="800" height="419" loading="lazy">
<em>Redirect requests back to the main bucket using HTTP protocol</em></p>
<p>Your buckets are now ready to go!</p>
<h3 id="heading-configure-domains-with-route53">Configure Domains with Route53</h3>
<p>So your website is up and running but only accessible via the bucket endpoint and not your custom domain. Let’s change that.</p>
<p>Head to <strong>Route53</strong>. If you’ve registered your domain with the Amazon Registrar you should see that a hosted zone has been setup for you with two record sets. One for Name Server (NS) and one for SOA.</p>
<p>All we need to do is to create two more record sets to point to the S3 bucket endpoints.</p>
<p>For each record set:<br> — Type: A — IPv4 address<br> — Alias: Yes<br> — Alias Target: the S3 website endpoint that matches what you set for Name.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/-pRXjHHB-EmOPzuTcNbKribluPQTsshaCGf-" alt="Image" width="562" height="324" loading="lazy">
<em>Creating a record set for www subdomain</em></p>
<p>Now we can head to the custom url…and voilà!<br>We’re almost there, but there’s one last thing we’re missing…</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/Tn5XmMFeKZDKn2zLITzmEfYtBOP6OH2ZSrVl" alt="Image" width="800" height="356" loading="lazy"></p>
<p><strong>Note</strong>: If your domain is registered with another domain registrar (not Amazon) you’ll need to follow some different steps to set this up. Usually you’ll need to add a CNAME record with a value of the main S3 buckets endpoint.</p>
<p><strong>Troubleshooting</strong>:<br>If you deleted the hosted zone Amazon created when you first registered the domain (I’ve done this because hosted zones do incur some charges), you’ll need to create a new hosted zone from scratch.</p>
<ol>
<li>Select “Create Hosted Zone” and set the domain name, for example “mywebsite.com”</li>
<li>This will generate some new record sets for types NS and SOA.</li>
<li>Go into your registered domain and update the Name Servers values to those generated in the new NS record set.</li>
</ol>
<h3 id="heading-requesting-a-certificate">Requesting a Certificate</h3>
<p>Awesome, the site is now hosted using the custom url! However we can only access it via HTTP protocol.<br>We should always ensure our sites are secured using HTTPS protocol. This protects our site and users from malicious injection attacks and guarantees authenticity.</p>
<p>Head to <strong>Certificate Manager</strong> in AWS Console and request a new public certificate (this is free). You’ll be prompted to enter the domain names you wish to secure.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/nklZPz8lBuVETFkAxoKadUuDn3PLvztVIH3J" alt="Image" width="633" height="435" loading="lazy"></p>
<p>Before the certificate can be issued, Amazon needs to be able to verify that you own the specified domains.</p>
<p>You can choose from two verification methods: Email or DNS.</p>
<p>Email is generally simpler, but you’ll need to ensure you can access the email used to register the domain. Alternatively, if you used Amazon Registrar and Route53, you can select the DNS method. This requires you to add some specific record sets to the hosted zone, but this is mostly automated for you so it’s quite simple.</p>
<p>It can take a few minutes for the certificate to be issued after validation.<br>When its all done we can continue to the final step!</p>
<h3 id="heading-configuring-cloudfront">Configuring CloudFront</h3>
<p>For the final step we are going to use <strong>CloudFront</strong> which allows us to use the new SSL certificate to serve the website with HTTPS. CloudFront also speeds up the distribution of web content by storing it at multiple edge locations and delivering from the closest edge location to a user.</p>
<p>We need <strong>two new web distributions</strong>, one for each S3 bucket. Head to CloudFront in the AWS Console and create the first web distribution.<br>There are lots of settings available to create a web distribution, but for the basics we only need to change five:</p>
<ol>
<li><strong>Origin Domain Name</strong>: Set this to the S3 website endpoint for one of the buckets. <strong>Important</strong>: This field will give you some auto-complete options with your S3 bucket names. However, using these can cause issues with redirecting to the bucket endpoint. So instead use the bucket endpoint directly.</li>
<li><strong>Origin Id</strong>: This populated for you when you enter Origin Domain Name.</li>
<li><strong>Viewer Protocol Policy</strong>: Set to “Redirect HTTP to HTTPS”.</li>
<li><strong>Alternate Domain Names</strong>: This should match the name of the S3 bucket you’re pointing to. For example “mywebsite.com”.</li>
<li><strong>SSL Certificate</strong>: Select “Custom SSL Certificate” and select your new certificate from the dropdown.</li>
</ol>
<p>Do this again for the second S3 bucket.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/yAhOQRit35ON9mB7rtO4aefi6w2o9r-RQ2p1" alt="Image" width="800" height="605" loading="lazy"></p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/AUfGClmx76ORz-sEOSipOFrJmBQE6KH2pDpf" alt="Image" width="800" height="511" loading="lazy"></p>
<p>The distributions can take a while to spin up, so while we wait, let’s do the finishing steps.</p>
<p>Back in <strong>S3</strong>, go to your secondary bucket (www.mywebsite.com), in the Properties tab and under Static Website Hosting set the redirect protocol to HTTPS.</p>
<p>Finally, head back to <strong>Route53</strong>. We need to update the custom A records we created to now target the CloudFront distributions rather than the S3 buckets. For each record, change the Alias Target and select the CloudFront distribution available in the dropdown.</p>
<p>Note: Again, if you are using another DNS service you’ll need to go update the CNAME record from there to point to the CloudFront domain name.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/9PtEunXXDJGvAsXD03ZFepeSNosGtlXC-SWl" alt="Image" width="800" height="392" loading="lazy">
<em>Huzzah!</em></p>
<p>And there you have it! Your beautiful website is now available at the custom domain and served with HTTPS!</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1q28QH0CERJRnMkDZdrmOImMQD7szHNf5xZI" alt="Image" width="496" height="360" loading="lazy">
_[From Giphy](https://giphy.com" rel="noopener" target="<em>blank" title=")</em></p>
<p>Thanks for reading! I hope this guide was useful and enjoyable, I’d love to know if you found it helpful.</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
