Where Does My Code Actually Go? A Look Inside AWS Amplify Deployments

You push to a branch, wait two minutes, and your site is live. But what happened in between?
Most people who use AWS Amplify never think about the plumbing. You connect a Git repo, hit deploy, and a URL shows up. That is the whole point of Amplify, so this is fine most of the time.
Then something breaks. A build fails for no clear reason. A page renders locally but 404s in production. Someone on your team asks where the files are actually stored, and you realize you have no idea.
This post walks through what Amplify is really doing under the hood. Where your source goes. Where your build lands. Which AWS services quietly do the work. By the end, you will have a mental model that makes debugging a lot less mysterious.
Amplify is an orchestrator, not a new thing
Here is the first idea worth getting straight. Amplify does not invent new infrastructure.
It is a coordination layer sitting on top of services you already know. CodeBuild, S3, CloudFront, Lambda, CloudFormation, Cognito. Amplify wires them together and manages them so you do not have to click through five consoles.
It also has two halves that deploy through completely different machinery, and mixing them up is where a lot of confusion starts.
Amplify Hosting is your frontend or full-stack web app. React, Next.js, Vue, a plain static site. It deploys through a Git-connected build pipeline that ends in S3 and CloudFront.
Amplify Backend is your auth, APIs, databases, storage, and functions. It deploys through CloudFormation. In Gen 1, you write CLI definitions. In Gen 2, you write TypeScript that compiles down to CloudFormation. Same destination, nicer authoring.
Keep those two tracks separate in your head and everything else gets easier.
What happens when you push a branch?
Say you push to your connected main branch. A webhook fires and Amplify kicks off a pipeline with four stages.
First it provisions a build environment. Under the hood, this is a CodeBuild-style managed container. It comes preloaded with Node and package managers. You do not manage it. It gets created for the build and destroyed right after.
Then it builds. Amplify clones your repo into that container and runs the phases from your amplify.yml file:
version: 1
frontend:
phases:
preBuild:
commands:
- npm ci
build:
commands:
- npm run build
artifacts:
baseDirectory: dist # or .next, build, out
files:
- '**/*'
cache:
paths:
- node_modules/**/*
That baseDirectory line matters more than people think. Whatever folder you point it at is what gets deployed. Everything else in the container gets thrown away. If your site deploys empty, this is the first place to look.
Next it deploys. The contents of your artifact folder get uploaded to an Amplify-managed S3 bucket. Your files physically live here now.
Finally, it distributes. Amplify points a CloudFront distribution at that bucket and pushes the new version to edge locations around the world. Cache invalidation happens automatically, so users see the fresh build instead of the old one.
The whole thing in one line: Git, then a build container, then S3, then CloudFront.
So where does everything actually live?
This is the question that started the whole post, so let me be precise about it.
Your source code never permanently lives inside Amplify. It stays in GitHub, GitLab, Bitbucket, or CodeCommit. Amplify only clones a copy into a throwaway container. When the build ends, that copy is gone.
Your build container is temporary by design. Nothing survives it except the cache you explicitly declare.
Your build cache, things like node_modules, sits in an Amplify-managed S3 bucket. This is why your second build is faster than your first.
Your build output, the actual deployed site, also lives in an Amplify-managed S3 bucket. This is the real origin of your app.
What your users hit is not S3 directly. It is CloudFront, the CDN edge cache, which pulls from S3 in the background.
And your build logs? Those go to Amazon CloudWatch. They also stream live in the Amplify console while the build runs.
One honest caveat. You usually cannot poke at the underlying S3 bucket or CloudFront distribution yourself. Amplify manages them and keeps them at arm’s length. You work through the console, the CLI, or amplify.yml instead.
Static site or server-rendered? The answer changes everything
Not every app deploys the same way. It depends on whether your framework needs a server at runtime.
A static site or single-page app is the simple case. Think Create React App, Vite, or plain HTML. There is no server running anywhere. Your files sit in S3 and CloudFront serves them. It is cheap and almost nothing can go wrong.
Server-rendered apps are different. With Next.js, some pages get built on demand. For those, Amplify provisions AWS Lambda functions to render on the fly. Increasingly it runs on regional Lambda through the Amplify Hosting compute layer.
Your static assets still go to S3 and CloudFront. But dynamic routes now hit Lambda. Amplify reads your framework’s build output and sets this up for you without much fuss.
How does the backend deploy?
Front end aside, what about auth, databases, and APIs?
That side never touches the hosting pipeline. Instead, Amplify turns your resource definitions into infrastructure-as-code and hands them to CloudFormation.
In Gen 1, running amplify push compiles your definitions into CloudFormation templates. CloudFormation then provisions the real services.
In Gen 2, your backend.ts runs through the AWS CDK. The CDK synthesizes CloudFormation stacks behind the scenes. You get a cleaner, TypeScript-first workflow and the same reliable result.
The services CloudFormation stands up depend on what you declared. Auth becomes Amazon Cognito, using User Pools and Identity Pools. A GraphQL API becomes AWS AppSync backed by DynamoDB. A REST API becomes an API Gateway with Lambda behind it. File storage becomes S3. Custom domains lean on Route 53 for DNS and Certificate Manager for TLS. Monitoring flows into CloudWatch.
Your backend state, meaning which resources exist and how they are configured, is tracked in CloudFormation stacks plus a deployment-state bucket that Amplify manages per environment.
A note for anyone running several apps at once
If you run multiple apps across different domains, a few Amplify realities are worth knowing early.
Each app can be its own Amplify project with its own pipeline and custom domain. Yet they can all point at a single shared Cognito User Pool. That means one account works everywhere. Cognito is the obvious home for a shared login system.
If you want a custom Node.js auth service instead of Cognito’s hosted flows, that usually lives as Lambda behind an API Gateway. Amplify Hosting is built for web frontends, not long-running Node servers, so your API layer belongs in the backend half.
The genuinely tricky part is cross-domain sessions. Cookies do not travel across domains. So a shared service normally issues JWT tokens that each app validates on its own, rather than leaning on one shared session cookie.
The short version
If you remember nothing else, remember this map.
Your code lives in Git and gets cloned into a disposable build container. Your build lands in an S3 bucket that Amplify manages. CloudFront serves it to users, with Lambda stepping in for server-rendered pages. A CodeBuild-style container does the building by running your amplify.yml. CloudFormation provisions the backend, driven by the CDK in Gen 2. And your logs sit in CloudWatch when you need to debug.
None of this is magic. It is a handful of familiar AWS services with a friendly wrapper on top. Once you can name each piece, a failed build stops being a black box and starts being a place you know how to look.
Found this useful? A clap and a follow help more people find it.