Deploy a 3-Tier App on AWS
Deploy a React frontend, Django API, and PostgreSQL database on AWS.
Introduction
30 Second Summary
You have built an application that works perfectly on your laptop. But the moment you close your terminal, nobody else in the world can use it.
In this project, you will deploy a full-stack restaurant ordering app to AWS, connecting a React frontend, Django backend, and PostgreSQL database across three managed cloud services that anyone can reach from a public URL.
What You'll Build
Share a single URL and watch someone use your complete 3-tier architecture in action: browsing a restaurant menu, adding items to their cart, and placing orders against a live cloud backend and database.
By the end of this project, you'll have:
- A live URL powered by AWS Amplify that loads your Bistro Cart frontend from a global CDN, automatically redeploying every time you push to GitHub.
- A public API endpoint on Elastic Beanstalk where your Django backend processes requests from anywhere in the world.
- Persistent application data on Amazon RDS that survives server restarts and redeployments without you managing any database infrastructure.
- Secret Mission: A GitHub Actions CI/CD pipeline that deploys your backend automatically on every push, matching your frontend's zero-touch deployment workflow.
Are there any prerequisites?
This project continues from your local Bistro Cart build. You need your React frontend and Django REST Framework backend each in their own GitHub repository, an AWS account with the CLI configured, and Python with pip installed.
Before We Start
Before we dive in, let's lock in what you're about to build and why it matters.
Install the EB CLI
Your Bistro Cart frontend and backend work perfectly on your local machine. To make them available to the world, you need to deploy them to AWS infrastructure.
The tool that handles backend deployment is the Elastic Beanstalk CLI (EB CLI). Your AWS CLI is already configured, so the EB CLI is the only missing piece. In this step you will install it with pip and confirm your credentials are ready to go.
In this step, get ready to:
- Install the EB CLI using pip.
- Confirm your AWS CLI credentials are working.
- Identify your target deployment region.
Install the EB CLI with pip
The EB CLI is a command-line tool built specifically for creating and managing Elastic Beanstalk environments. You install it the same way you install any Python package.
- Install the EB CLI by running this command:
pip install awsebcli --upgrade --user
What does this command do?
- pip install awsebcli downloads and installs the Elastic Beanstalk CLI package.
- --upgrade ensures you get the latest version if an older one is already installed.
- --user installs to your user directory so you do not need admin privileges or risk changing system libraries.
You should see pip downloading and installing packages. Once it finishes, you are ready to verify the install.
Before you run the next command, what do you think you will see if the install succeeded?
- Verify the EB CLI is available by running:
eb --version
✔️ I see a version number
You should see output similar to:
EB CLI 3.21.0 (Python 3.12)
Your exact version numbers may differ. As long as you see EB CLI 3.x.x the install is successful. Move on to the next substep.
ⓧ Command not found
This means the EB CLI executable is not on your system PATH. The fix depends on your operating system.
macOS
On macOS with a standard framework Python 3.12, pip installs user-level executables to ~/Library/Python/3.12/bin. Add this directory to your PATH.
- Open your shell profile by running:
open ~/.zshrc
- Add this line at the end of the file:
export PATH="$HOME/Library/Python/3.12/bin:$PATH"
- Save the file and reload your shell profile by running:
source ~/.zshrc
- Run eb --version again to confirm it works.
Using a different Python version?
Replace 3.12 in the path with your actual Python version number. Run python3 --version to check.
Windows
On Windows with Python 3.12, pip installs user-level scripts to %USERPROFILE%\AppData\Roaming\Python\Python312\Scripts. You need to add this to your PATH.
- Press the Windows key and type environment variables.
- Click Edit environment variables for your account.
- Select Path and click Edit.
- Click New and paste: %USERPROFILE%\AppData\Roaming\Python\Python312\Scripts.
- Click OK twice to save.
- Close and reopen your Command Prompt or PowerShell window.
- Run eb --version again to confirm it works.
Using a different Python version?
Replace Python312 in the path with your version (e.g., Python311 for Python 3.11). Run python --version to check.
Confirm your AWS credentials
The EB CLI uses the same credentials as your AWS CLI. A quick check now saves debugging later if something is misconfigured.
- Confirm your AWS credentials are working by running:
aws sts get-caller-identity
You should see a JSON response containing your Account, UserId, and Arn. This confirms that your terminal can authenticate with AWS.
What do these fields mean?
- Account is your 12-digit AWS account number.
- Arn is the Amazon Resource Name of the IAM user or role your CLI is using.
- UserId is the unique identifier for that user or role.
- Decide which AWS region you want to deploy to. A common choice is us-east-1 (N. Virginia) because it supports all services and often has the lowest latency for demos.
- Note your chosen region. You will use it in the next step when initializing Elastic Beanstalk.
Seeing an error from aws sts get-caller-identity?
- If you see Unable to locate credentials, your AWS CLI is not configured. Run aws configure and enter your Access Key ID, Secret Access Key, default region, and output format.
- If you see ExpiredToken, your session credentials have expired. Refresh them through your identity provider or re-run aws configure with updated keys.
Help me troubleshoot my AWS CLI credentials.
Your tools are locked and loaded. Next up, you will deploy your React frontend to AWS Amplify and see it live at a real URL.
Deploy the Frontend to AWS Amplify
Your development environment is ready and your AWS credentials are confirmed working. Now it's time to get your Bistro Cart app live on the internet.
Deploying the frontend first gives you an immediate, visible result. Within minutes, your React app will be running at a real URL that anyone can visit.
In this step, get ready to:
- Connect your Bistro Cart frontend GitHub repository to AWS Amplify.
- Verify the live deployment loads in your browser.
Connect your frontend repo to Amplify
AWS Amplify is a managed hosting service that deploys your frontend to a global CDN. You connect a GitHub repository, and Amplify automatically builds and deploys your app every time you push code.
- Open the AWS Amplify console at https://console.aws.amazon.com/amplify/apps.
- Click Create new app.
- On the Start building with Amplify page, under Deploy your app, select GitHub.
- Click Next.
What happens during GitHub authorization?
A new browser tab opens asking you to authorize the Amplify GitHub App. This grants Amplify read access to your repository so it can pull your code for builds.
Choose Only select repositories and pick your Bistro Cart frontend repo. Then click Install & Authorize.
- After authorization, you are redirected back to the Amplify console. On the Add repository branch page, select your Bistro Cart frontend repository.
- Select the main branch.
- Click Next.
Amplify inspects your repository and auto-detects the build settings for your Vite project.
- On the Configure build settings page, confirm the build command is npm run build and the output directory is dist.
- Click Next.
- On the Review page, confirm your repository details and app settings are correct.
- Click Save and deploy.
What does Amplify do after you click Save and deploy?
Amplify clones your repository, installs dependencies, runs npm run build, and deploys the dist folder to a global CDN. It also sets up a webhook on your GitHub repo so every future push to main triggers an automatic redeploy.
The initial build takes 2-5 minutes. You can watch the build progress in the Amplify console.
Build failing?
If the build fails, click on the failed build in the Amplify console to view the build logs. Common causes include a missing package.json in the repo root, or a build script that references files not committed to GitHub.
Confirm your frontend code runs locally with npm run build before pushing. Help me debug my Amplify build failure.
Verify the deployment
- Wait for the build status to show a green checkmark indicating success.
- Click Visit deployed URL to open your live frontend in a new tab.
You should see your Bistro Cart frontend loaded at a URL like https://main.xxxxxxxxxx.amplifyapp.com. The page renders because Amplify is serving your static files from its CDN.
Before you interact with the app, what do you think will happen when you try an action that needs backend data, like browsing the menu or adding to cart?
- Open your browser developer tools by pressing F12 (or Cmd+Option+I on macOS).
- Switch to the Console or Network tab.
- Try an action that calls the backend API (browse the menu, add an item to cart, or log in).
You will see network errors in the console. The API requests fail because there is no backend server to respond to them.
Why do the API calls fail?
Your frontend is live, but it is trying to call a backend API that does not exist on the internet yet. The backend is still only on your local machine. Deploying the backend to Elastic Beanstalk is exactly what the next step solves.
Don't see your frontend at the Amplify URL?
Confirm the build completed successfully in the Amplify console (green checkmark, not a red X).
If you see a blank page, check that your index.html references relative paths (./assets/) instead of absolute paths (/assets/). Vite's default output uses relative paths, so this is usually only an issue if you changed the base config.
Help me troubleshoot why my Amplify deployment shows a blank page.
Your frontend is live on the internet. Anyone with the URL can see it. Next up, you will deploy the Django backend to Elastic Beanstalk so those API calls have somewhere to go.
Deploy Django to Elastic Beanstalk
Your frontend is live on Amplify, but every API call from it falls into a void. The backend needs a home that is always available, auto-scales, and requires minimal server management.
Elastic Beanstalk is AWS's Platform-as-a-Service for web applications. You give it your code and it provisions the EC2 instance, load balancer, and auto-scaling group for you. In this step, you will prepare your Django project for EB, deploy it, and confirm the API is reachable at a public URL.
In this step, get ready to:
- Configure your Django project for Elastic Beanstalk with a WSGI path file.
- Initialize and create an EB environment using the EB CLI.
- Verify your deployed backend loads the DRF browsable API at a public URL.
Prepare your Django project for Elastic Beanstalk
Elastic Beanstalk needs two things from your Django project to run it correctly: a configuration file that points to your WSGI entry point, and a requirements.txt that includes a production-grade WSGI server.
- Open your terminal and navigate to your backend repository root folder.
- Create the .ebextensions folder by running this command:
mkdir .ebextensions
- Confirm the folder was created by running:
ls -a
You should see .ebextensions listed in the output.
- Create the EB configuration file by running:
touch .ebextensions/django.config
- Open .ebextensions/django.config in your code editor and paste the following:
option_settings:
aws:elasticbeanstalk:container:python:
WSGIPath: [[WSGI_MODULE="your Django project folder name (e.g. bistrocart)"]].wsgi:application
What does this configuration do?
The WSGIPath tells Elastic Beanstalk where your application's WSGI entry point lives. Replace the placeholder with your actual Django project module name. This is the folder that contains your wsgi.py file.
For example, if your Django project folder is called bistrocart, the WSGIPath would be bistrocart.wsgi:application.
- Save .ebextensions/django.config.
Not sure which folder name to use?
Look for the folder in your backend repo that contains wsgi.py, settings.py, and urls.py. That folder's name is what goes before .wsgi:application.
Help me find my Django WSGI module name
Next, your requirements.txt needs to include gunicorn, the production WSGI server that Elastic Beanstalk uses to serve your Django app.
- Open requirements.txt in your code editor.
- Check if gunicorn is already listed. If not, add it on a new line:
gunicorn
- Save requirements.txt.
Why gunicorn?
Django's built-in development server (manage.py runserver) is not designed for production traffic. Gunicorn is a production-ready WSGI HTTP server that handles concurrent requests efficiently. Elastic Beanstalk automatically uses it when it finds it in your requirements.
Initialize and deploy with EB CLI
With your project configured, you can now initialize it as an Elastic Beanstalk application and create a running environment.
- Initialize your EB application by running this command from your backend repo root:
eb init -p python-3.12 bistro-cart-backend --region us-east-1
What does this command do?
This creates an Elastic Beanstalk application named bistro-cart-backend in the us-east-1 region. The -p python-3.12 flag specifies the platform and skips all interactive prompts. EB CLI uses defaults for everything else.
You will see a new .elasticbeanstalk folder appear in your project. This holds EB CLI configuration locally.
- Confirm initialization succeeded by checking for the new folder:
ls .elasticbeanstalk
You should see a config.yml file listed.
- Now create your environment by running:
eb create bistro-cart-env
What happens during eb create?
Because you provided the environment name (bistro-cart-env) directly in the command, EB CLI skips all interactive prompts. It automatically provisions an EC2 instance, a load balancer, an auto-scaling group, and a security group.
This takes approximately 5 minutes. The terminal prints status events as resources are created. Wait for it to finish before proceeding.
- Once creation completes, get your environment URL by running:
eb status
Look for the CNAME field in the output. It looks like bistro-cart-env.us-east-1.elasticbeanstalk.com. This is your backend's public URL.
Copy your CNAME now
Save this URL somewhere accessible. You will need it in a moment to update ALLOWED_HOSTS and later to connect your frontend.
Django rejects requests from hostnames it does not recognize. You need to add your new EB domain to ALLOWED_HOSTS in settings.py.
- Open your Django settings.py file.
- Find the ALLOWED_HOSTS list and add your EB environment CNAME. For example:
ALLOWED_HOSTS = [
'localhost',
'127.0.0.1',
# Add your Elastic Beanstalk domain
[[EB_DOMAIN="your-environment-cname.us-east-1.elasticbeanstalk.com"]],
]
- Save settings.py.
- Commit your changes and deploy the update by running these commands:
git add .
git commit -m "Configure for Elastic Beanstalk deployment"
eb deploy
Why commit before deploying?
The EB CLI deploys the latest commit from your current Git branch. Any uncommitted changes will not be included in the deployment. Always commit before running eb deploy.
Wait for the deployment to complete. The terminal prints progress events.
Seeing a deployment failure?
Common causes include a typo in your WSGIPath (check that the module name matches your project folder exactly) or a missing package in requirements.txt. Run eb logs to see the deployment log and look for lines marked [ERROR].
Help me debug my EB deployment failure
Test the deployed backend
Your backend should now be running on Elastic Beanstalk. Before you run the check, what do you expect to see when you visit the EB URL in your browser?
- Open your deployed backend in the browser by running:
eb open
The Django REST Framework browsable API root page loads at your EB URL. You should see the familiar DRF interface with your API endpoints listed.
What about database endpoints?
If you try endpoints that query the database, you may see errors. This is because Django's default SQLite database lives on the EB instance's local filesystem. It works for now, but here is the catch: the filesystem is ephemeral. Every time you redeploy, the instance resets and your data disappears.
The next step solves this by connecting a managed PostgreSQL database on RDS that persists independently of your application server.
✔️ Awesome, I've got everything!
Great. Double check you have saved all your files and that your latest commit includes the .ebextensions/django.config, updated requirements.txt, and the ALLOWED_HOSTS change in settings.py.
ⓧ I'd like to double check the full code
Your .ebextensions/django.config should contain exactly:
option_settings:
aws:elasticbeanstalk:container:python:
WSGIPath: your_project.wsgi:application
Replace your_project with your actual Django project module name.
Your requirements.txt must include gunicorn alongside all your other project dependencies.
Your settings.py must have your EB CNAME in ALLOWED_HOSTS.
Your Django backend is now live on Elastic Beanstalk. Next up, you will connect a managed PostgreSQL database so your data persists across deployments instead of vanishing every time you redeploy.
Add RDS PostgreSQL and Connect to Django
In the previous step, you saw your Django backend load successfully on Elastic Beanstalk. But there is a hidden problem lurking beneath the surface.
Elastic Beanstalk's filesystem is ephemeral. Every time you deploy, the instance resets. That means your SQLite database disappears along with it. In this step, you will provision a managed PostgreSQL database on Amazon RDS and wire Django to use it as a persistent, production-grade data store.
In this step, get ready to:
- Create an RDS PostgreSQL instance in the same VPC as your Elastic Beanstalk environment.
- Configure Django to connect to RDS using environment variables.
- Set up automatic database migrations on every deploy.
Create an RDS PostgreSQL instance
Your Django backend needs a database that persists independently of the application server. RDS gives you a fully managed PostgreSQL instance that handles backups, patching, and availability.
- Open the Amazon RDS console at https://console.aws.amazon.com/rds/.
- In the upper-right corner, confirm your region is set to us-east-1 (the same region as your EB environment).
- In the navigation pane, choose **Databases**.Databases.
- Click Create database.
- Under Choose a database creation method, select Standard create.
- Under Engine options, for Engine type, choose PostgreSQL.
- Under Templates, select Free tier.
What does the Free tier template do?
Selecting Free tier pre-configures a db.t3.micro instance class with 20 GB of General Purpose SSD (gp2) storage. This keeps your database within the AWS Free Tier allowance of 750 hours per month for Single-AZ Micro DB instances.
- Under Settings, for DB instance identifier, enter bistro-cart-db.
- For Master username, enter a username you will remember (e.g. bistro_admin).
- Uncheck Auto generate a password.
- For Master password and Confirm password, enter a secure password. Save this password somewhere safe. You will need it shortly.
Save your credentials now
Write down or copy your master username and password immediately. You will not be able to retrieve the password from AWS later. You will need both values to configure Django's database connection.
- Under Connectivity, for Virtual private cloud (VPC), select the same VPC that your Elastic Beanstalk environment uses.
- For Public access, select No.
- Under VPC security group, choose the security group associated with your EB environment. This allows your EB instances to reach the database on port 5432.
- Under Additional configuration, for Initial database name, enter bistrocartdb.
- Leave all other settings at their defaults.
- Click Create database.
The database takes 5-10 minutes to become available. While it is being created, you will configure your Django project.
- Once the status changes to Available, click on your bistro-cart-db instance in the Databases list.
- Open the Connectivity & security tab.
- Copy the Endpoint value (it looks like bistro-cart-db.xxxxxxxxxxxx.us-east-1.rds.amazonaws.com). You will need this in the next substep.
- Note the Port value (default is 5432).
Configure Django to use RDS
Now you need to tell Django where to find the database. You will pass connection details as environment variables through Elastic Beanstalk, then update your Django settings to read them.
- Open the Elastic Beanstalk console at https://console.aws.amazon.com/elasticbeanstalk.
- In the navigation pane, choose Environments, then click bistro-cart-env.
- In the navigation pane, choose Configuration.
- In the Updates, monitoring, and logging configuration category, choose Edit.
- Scroll down to Environment properties.
- Click Add environment variable and add each of the following five variables, one at a time:
- RDS_DB_NAME = bistrocartdb (the initial database name you set during creation).
- RDS_USERNAME = your master username (e.g. bistro_admin).
- RDS_PASSWORD = the master password you saved earlier.
- RDS_HOSTNAME = the Endpoint you copied from the RDS console.
- RDS_PORT = 5432.
- Click Apply to save the environment properties.
You should see the environment updating briefly as it applies the new properties to your instances.
Why environment variables instead of hardcoding?
Storing database credentials in environment variables keeps them out of your source code. This means you can safely push to GitHub without exposing secrets. It also means you can use different databases for different environments (local SQLite for development, RDS PostgreSQL for production) without changing code.
Next, update your Django project to read these variables. Open settings.py in your backend repository.
- Find the existing DATABASES configuration in settings.py and replace it with the following:
import os
if 'RDS_DB_NAME' in os.environ:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.environ['RDS_DB_NAME'],
'USER': os.environ['RDS_USERNAME'],
'PASSWORD': os.environ['RDS_PASSWORD'],
'HOST': os.environ['RDS_HOSTNAME'],
'PORT': os.environ.get('RDS_PORT', '5432'),
}
}
else:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
What does this code do?
This configuration checks whether the RDS_DB_NAME environment variable exists. On Elastic Beanstalk, it will find the variables you just set and connect to PostgreSQL. Locally, where those variables do not exist, Django falls back to SQLite so your development workflow stays the same.
Make sure the import os line is at the top of your settings.py file. If it is already there, do not duplicate it.
- Add psycopg2-binary to your requirements.txt file. This is the PostgreSQL driver that Django needs to talk to RDS:
psycopg2-binary
- Open .ebextensions/django.config and add a container_commands section below the existing option_settings section. The full file should now look like this:
option_settings:
aws:elasticbeanstalk:container:python:
WSGIPath: your_project.wsgi:application
container_commands:
01_migrate:
command: "source /var/app/venv/*/bin/activate && python manage.py migrate"
leader_only: true
What does container_commands do?
The container_commands section runs after your source code is deployed but before the application starts. The 01_migrate command activates the virtual environment and runs Django's database migrations to create your tables in RDS.
The leader_only: true flag ensures migrations run on only one instance when you have multiple servers. This prevents race conditions where two instances try to migrate simultaneously.
✔️ Awesome, I've got everything!
Great. Double check you have saved all three files: settings.py, requirements.txt, and .ebextensions/django.config.
ⓧ I'd like to double check the full code
Your .ebextensions/django.config file should look exactly like this:
option_settings:
aws:elasticbeanstalk:container:python:
WSGIPath: your_project.wsgi:application
container_commands:
01_migrate:
command: "source /var/app/venv/*/bin/activate && python manage.py migrate"
leader_only: true
Your settings.py DATABASES section should look like this:
import os
if 'RDS_DB_NAME' in os.environ:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.environ['RDS_DB_NAME'],
'USER': os.environ['RDS_USERNAME'],
'PASSWORD': os.environ['RDS_PASSWORD'],
'HOST': os.environ['RDS_HOSTNAME'],
'PORT': os.environ.get('RDS_PORT', '5432'),
}
}
else:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
Your requirements.txt should include psycopg2-binary alongside your existing dependencies.
- Commit all three changes and deploy by running these commands:
git add .
git commit -m "Add RDS PostgreSQL configuration"
eb deploy
The deploy takes a few minutes. Elastic Beanstalk will install psycopg2-binary, then run your migration command before starting the application.
Deploy failing with a database connection error?
Check that your RDS instance status is Available in the RDS console. If it is still creating, wait for it to finish before deploying.
Verify the five environment variables in the EB console match your RDS details exactly. A single typo in the hostname or password will cause a connection failure.
Confirm the RDS instance uses the same VPC and security group as your EB environment. If they are in different VPCs or security groups, the instances cannot reach the database on port 5432.
help me debug my Elastic Beanstalk deploy failing with a database connection error to RDS
Verify database connectivity
Before you check the result, what do you expect to see in the deploy logs? Think about what the container_commands section does during deployment.
- Check the migration output by running:
eb logs
Scroll through the output and look for lines containing migration information. You should see output like:
Applying migrations... OK
This confirms Django successfully connected to your RDS PostgreSQL instance and created the database tables.
- Visit your backend URL by running:
eb open
- Test a database-dependent endpoint (e.g. user registration, or any endpoint that reads or writes data). Confirm it returns a successful response.
Now test that data actually persists across deploys.
- Deploy again without making any changes:
eb deploy
- After the deploy completes, visit your backend URL again and confirm the data you created earlier is still there.
Your data persists because it now lives in RDS, completely independent of the EB instance filesystem. No more disappearing databases on redeploy.
Why is persistence the real test?
With SQLite on EB, data appeared to work at first. It only disappeared after a redeploy wiped the filesystem. By redeploying and confirming your data survives, you have proven the RDS connection is working correctly as a production database.
Your backend now has a rock-solid database that will not disappear. Next up, you will connect the frontend to the backend so the full Bistro Cart app works end-to-end.
Connect the Tiers
Your backend is running on Elastic Beanstalk with a persistent PostgreSQL database on RDS. Data survives redeploys, and your API responds at a public URL.
But your frontend still points to a local development server. When someone visits your Amplify URL and triggers an API call, the request goes nowhere useful. In this step, you will point the frontend at the backend and configure CORS so the browser allows the cross-origin requests.
In this step, get ready to:
- Update the frontend API base URL to point to your Elastic Beanstalk endpoint.
- Configure CORS on the backend to allow requests from your Amplify domain.
- Verify the full 3-tier app works end-to-end.
Update the frontend API configuration
Your React frontend makes API requests to a base URL. During local development this pointed to http://localhost:8000 or similar. You need to swap it to your Elastic Beanstalk URL so the deployed frontend reaches the deployed backend.
- In your frontend code, locate where your API base URL is defined. This is typically in an .env file, a config file like src/config.ts, or wherever you set the URL your app uses for API calls.
- Replace the local development URL with your Elastic Beanstalk environment URL by updating it to:
http://[[EB_ENV_URL="your-env.us-east-1.elasticbeanstalk.com"]]
Where do I find my EB environment URL?
Run eb status in your backend repo terminal. The CNAME field shows your environment URL. Use that value (with http:// in front) as your API base URL.
- Save the file.
- Commit and push to GitHub by running these commands in your frontend repo:
git add .
git commit -m "Point API base URL to Elastic Beanstalk"
git push origin main
Amplify detects the push and automatically redeploys your frontend. Wait 2-3 minutes for the build to complete.
- Visit your Amplify URL once the deploy finishes.
- Open your browser developer tools by pressing F12 (or Cmd+Option+I on macOS).
- Click on the Console tab.
- Try an action that calls the API (browse the menu, add an item to cart).
You will see red error messages in the console mentioning a CORS policy violation. The browser is blocking the request because the backend has not been told to accept requests from your Amplify domain yet.
What is CORS?
CORS (Cross-Origin Resource Sharing) is a browser security feature. When your frontend at https://main.xxx.amplifyapp.com calls your backend at http://your-env.elasticbeanstalk.com, those are two different origins.
The browser refuses to share response data across origins unless the backend explicitly says "I trust that origin." That is what you will configure next.
Configure CORS on the backend
You need to tell Django to include CORS headers that allow your Amplify domain. The django-cors-headers package handles this.
- In your backend repo, ensure django-cors-headers is listed in requirements.txt. If it is not already there, add it by opening requirements.txt and adding this line:
django-cors-headers
Already have it?
If your project already used django-cors-headers for local development with your React frontend, it may already be in requirements.txt and partially configured. Confirm it is present before proceeding.
- Open settings.py and confirm "corsheaders" is in your INSTALLED_APPS list. If it is not, add it:
INSTALLED_APPS = [
# ... your other apps
"corsheaders",
]
- Confirm "corsheaders.middleware.CorsMiddleware" is in your MIDDLEWARE list, placed as high as possible (before "django.middleware.common.CommonMiddleware"). If it is not, add it near the top:
MIDDLEWARE = [
"corsheaders.middleware.CorsMiddleware",
"django.middleware.common.CommonMiddleware",
# ... your other middleware
]
Why does the middleware go at the top?
CorsMiddleware must run before any middleware that generates responses. If it runs after CommonMiddleware, it cannot attach the CORS headers to those responses. Placing it first guarantees every response gets the correct headers.
- Add your Amplify domain to CORS_ALLOWED_ORIGINS in settings.py. Add this setting (or update it if it already exists):
CORS_ALLOWED_ORIGINS = [
"[[AMPLIFY_DOMAIN=\"https://main.xxxxxxxxxx.amplifyapp.com\"]]",
]
What does this code do?
CORS_ALLOWED_ORIGINS tells Django which origins are trusted to make cross-site requests. Your Amplify URL is the only frontend that should call this backend, so it is the only origin you add.
Use the exact URL from your Amplify console (including https://, no trailing slash). You can find it on the Amplify app overview page.
- Save settings.py and requirements.txt.
- Commit and deploy to Elastic Beanstalk by running these commands:
git add .
git commit -m "Add CORS configuration for Amplify frontend"
eb deploy
- Wait for the deployment to complete. You will see Environment update completed successfully in the terminal output.
Deployment failed?
- Run eb logs and look for import errors. If you see a ModuleNotFoundError for corsheaders, confirm django-cors-headers is spelled correctly in requirements.txt and redeploy.
- Check that "corsheaders" in INSTALLED_APPS has a trailing comma after it. A missing comma causes Django to fail on startup.
Help me debug my EB deployment with CORS configuration.
End-to-end verification
Both the frontend and backend are now configured to talk to each other. Time to test the full loop.
Before you test, what do you expect to happen when you click a button that fetches menu data from the backend? Think about the path the request will take: browser to Amplify CDN, API call to Elastic Beanstalk, database query to RDS, response back.
- Visit your Amplify frontend URL in your browser.
- Perform an action that triggers an API call. For example, browse the menu or add an item to your cart.
- Confirm the data loads successfully from your RDS database through the Elastic Beanstalk backend.
Your 3-tier deployment is live. The frontend served from Amplify calls the backend on Elastic Beanstalk, which reads and writes data to RDS PostgreSQL.
Still seeing CORS errors?
- Double check the exact URL in CORS_ALLOWED_ORIGINS. It must match your Amplify URL exactly, including the scheme (https://) and with no trailing slash.
- Confirm "corsheaders.middleware.CorsMiddleware" is at the top of your MIDDLEWARE list, before CommonMiddleware.
- Make sure the eb deploy completed successfully. Run eb status and confirm the environment status is Ready.
Help me fix CORS errors between my Amplify frontend and Elastic Beanstalk backend.
Secret mission
Automate Backend Deploys with GitHub Actions
Right now you deploy manually with eb deploy every time you change your backend. What if every push to your main branch automatically deployed to Elastic Beanstalk, just like Amplify auto-deploys your frontend? Set up a CI/CD pipeline using the official aws-elasticbeanstalk-deploy GitHub Action with secure OIDC authentication.
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely. The Elastic Beanstalk environment and RDS instance run continuously and may incur charges depending on your AWS Free Tier eligibility.
Cost warning
The EB environment runs an EC2 instance and load balancer continuously. The RDS PostgreSQL instance also runs continuously. If your account was created before July 15, 2025, these are covered under the 12-month free tier. If your account is newer or past the free tier window, expect approximately $15-25/month combined.
If you are not actively using this deployment, choose Pause or Delete below to stop charges.
Resources you used:
- AWS Amplify app (frontend hosting with CI/CD from GitHub).
- Elastic Beanstalk environment bistro-cart-env (EC2 instance, load balancer, auto-scaling group).
- RDS PostgreSQL instance (db.t3.micro, 20 GB storage).
- IAM role with OIDC trust policy for GitHub Actions (Secret Mission).
Keep everything running
No action needed. Choose this if you are still actively building or want to keep the deployment live for demo purposes.
- Monitor your costs in the AWS Billing console to avoid surprises.
- The Amplify app stays within its free tier (1,000 build minutes, 5 GB storage, 15 GB data served per month) for normal usage.
- The EB CLI remains installed on your machine.
Pause - I'll come back to this later
Shut down running compute to avoid charges while keeping your data and configuration intact.
- Terminate the Elastic Beanstalk environment to stop EC2 and load balancer charges. Navigate to your backend repo directory in the terminal and run:
- You can recreate the environment later with eb create using the same configuration.
eb terminate bistro-cart-env
- Stop the RDS instance temporarily to avoid compute charges while keeping your data.
- Open the RDS console at https://console.aws.amazon.com/rds/.
- In the navigation pane, choose Databases, then select your PostgreSQL instance.
- Choose Actions, then Stop temporarily.
- Acknowledge the auto-restart warning (the instance restarts automatically after 7 days) and confirm.
- You will still be billed for storage while the instance is stopped.
What about Amplify?
The Amplify app uses free tier allowances and does not incur compute charges while idle. You can leave it running. It will show errors when calling the backend since the EB environment is terminated.
Delete - I don't want to use this again
Remove all project resources permanently. This cannot be undone.
- Terminate the Elastic Beanstalk environment by running this command from your backend repo directory:
eb terminate bistro-cart-env
- When prompted, type the environment name to confirm termination.
- Delete the RDS PostgreSQL instance.
- Open the RDS console at https://console.aws.amazon.com/rds/.
- In the navigation pane, choose Databases, then select your PostgreSQL instance.
- Choose Actions, then Delete.
- Uncheck Create final snapshot? (for a learning project, you do not need a final snapshot).
- Enter delete me in the confirmation box and choose Delete.
- Delete the Amplify app.
- Open the Amplify console at https://console.aws.amazon.com/amplify/apps.
- Select your Bistro Cart frontend app.
- In the left navigation, choose App settings, then General settings.
- Choose Delete app.
- Enter delete in the confirmation window and choose Delete app.
- Delete the IAM role created for GitHub Actions OIDC.
- Open the IAM console at https://console.aws.amazon.com/iam/.
- In the navigation pane, choose Roles.
- Search for the role you created for GitHub Actions, select it, and choose Delete.
- Enter the role name to confirm deletion.
That's a Wrap!
That's a Wrap!
Nice work! You've just deployed a full 3-tier restaurant application to AWS, taking Bistro Cart from local development to production infrastructure that anyone in the world can access with a URL.
You've learned how to:
- Deployed a React/Vite frontend to AWS Amplify with automatic CI/CD from GitHub, so every push redeploys your app to a global CDN.
- Deployed a Django REST Framework backend to Elastic Beanstalk with proper WSGI configuration and environment variables for production.
- Provisioned a managed PostgreSQL database on Amazon RDS and connected it to Django, solving the ephemeral filesystem problem with a persistent data layer.
- Secret Mission: Set up GitHub Actions for automatic backend deployments using the official EB deploy action with IAM OIDC authentication.
Ready to quiz yourself?