React: From Implementation to Deployment

Create your own React app with our step-by-step deployment guide.

Jun 7, 2024

When working with React, the eventual excitement lies in deploying your web application to the cloud. It's the moment you get to showcase your work to the world! This guide removes the mystery from deployment. In no time, you'll build a Docker image and deploy your React app to AWS ECS, transforming your local project into a professional and scalable cloud application.

When working with React, the eventual excitement lies in deploying your web application to the cloud. It’s the moment you get to showcase your work to the world!

In the following steps, you’ll discover how to deploy your React application. I’m assuming a few things:

  • You are familiar with Nextjs and React.
  • You have a basic familiarity with Docker.
  • You have a basic understanding of AWS.

Feel free to ping me if you face any issues, let’s get started.


Step 1: Setting up the project

As usual, let’s start by cloning the project:

git clone https://github.com/garciadiazjaime/tutorial-react-deployment-options

Let’s move inside the project:

cd tutorial-react-deployment-options

Let’s install the dependencies:

npm install

up to date, audited 281 packages in 470ms

106 packages are looking for funding
run `npm fund` for details

found 0 vulnerabilities

Now, let’s run the server:

npm run dev

> tutorial-react-deployment-options@0.1.0 dev
> next dev

▲ Next.js 14.0.2
- Local: http://localhost:3000

✓ Ready in 2.8s

Open localhost:3000 and you should see something like this:


Fig. 1. Hello World, clean nextjs installation.


iJS Newsletter

Keep up with JavaScript’s latest news!


This project uses Next.js and, and only displays the classic ‘Hello, world!’ message on the homepage:

If you open ‘page.tsx’, you’ll find the following code:

Listing 1. Simple Next.js application displaying Hello World

export default function Home() {
 return (
  <main style={{ textAlign: "center", marginTop: "10vh" }}> 
   <h1>Hello world</h1> 


The primary aim here is to guide you through the deployment process. At this stage, the functionality or appearance of the app doesn’t hold much significance.

Step 2: Create a Docker image

The script npm run dev starts a server locally, which works great for local development. However, the most common practice for production involves containerizing the application using Docker. Let’s proceed to create a Docker image for our React application.

Let’s go to the Docker website: https://www.docker.com/get-started/, and click the blue button “Download”, make sure to choose the right version.

Fig. 2: Docker website, download button.

This process will initiate the download of a Docker image onto your machine. After it’s downloaded, proceed with the installation. Once installed, open the Docker Desktop application. It will prompt you to create an account; please do so. Finally, you should see the Docker Desktop interface with your user account logged in.

Fig 3: Docker Desktop user dashboard.

You should notice your username displayed there. I won’t delve into extensive details about the Docker installation, as there are many comprehensive tutorials available for that. The key here is to ensure Docker is installed and that you’ve set up a Docker Hub account.

To verify that Docker is running, let’s try the next command:

docker --version 

Docker version 24.0.5, build ced0996

That command will display the Docker version installed on your machine, you can see that mine is version 24. If it throws an error, you might need to debug your installation. Feel free to reach out; I’m here to help.

Now, let’s set up two files: Dockerfile and .dockerignore.

The Dockerfile is a configuration file that outlines the steps required to install and run the project.

The .dockerignore file is a list of files the container should ignore, much like .gitignore.

If you open the Dockerfile, you’ll find the following lines:

Listing 2: Dockerfile extracted from nextjs

# Dockerfile

FROM --platform=linux/amd64 node:20-alpine AS base

# Install dependencies only when needed
FROM base AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat

# Install dependencies based on the preferred package manager
COPY package.json package-lock.json ./
RUN npm ci

# Rebuild the source code only when needed
FROM base AS builder
COPY --from=deps /app/node_modules ./node_modules
COPY . .

RUN npm run build

# Production image, copy all the files and run next
FROM base AS runner

ENV NODE_ENV production

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --from=builder /app/public ./public

# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:nodejs .next

# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs


# set hostname to localhost

# server.js is created by next build from the standalone output
# https://nextjs.org/docs/pages/api-reference/next-config-js/output
CMD ["node", "server.js"]



The iJS React Track


The Dockerfile might seem complex, but in a nutshell, it does the following:

  1. Installs Node version: 20.
  2. Copies package-lock.json and installs npm dependencies.
  3. Runs script npm run build which generates a nextjs standalone project version.
  4. Copies static files. Despite the simplicity of our app, it relies on some asset files visible in the ‘network’ tab of the developer tools panel, mainly javascript files.
  5. Sets the hostname, port, and initiates the server.

Alright, these are the instructions to build the docker image, let’s create one by running the following command:

Note: Notice how I’m adding my Docker Hub username: garciadiazjaime, please change that for your Docker Hub username.

docker build -t garciadiazjaime/nextjs-docker .

[+] Building 118.0s (22/22) FINISHED                                                                          docker:desktop-linux
=> [internal] load build definition from Dockerfile                                                                          0.0s
=> => transferring dockerfile: 1.47kB                                                                                        0.0s
=> => transferring context: 135B                                                                                             0.0s
=> [internal] load metadata for docker.io/library/node:20-alpine                                                             2.0s
=> [auth] library/node:pull token for registry-1.docker.io                                                                   0.0s
=> [base 1/1] FROM docker.io/library/node:20-alpine@sha256:b1789b7be6aa16afd642eaaaccdeeeb33bd8f08e69b3d27d931aa9665b731f01  0.0s
=> [internal] load build context                                                                                             1.1s
=> => transferring context: 3.13MB                                                                                           1.0s
=> CACHED [builder 1/4] WORKDIR /app                                                                                         0.0s
=> CACHED [deps 1/4] RUN apk add --no-cache libc6-compat                                                                     0.0s
=> CACHED [deps 2/4] WORKDIR /app                                                                                            0.0s
=> CACHED [deps 3/4] COPY package.json package-lock.json ./                                                                  0.0s
=> CACHED [deps 4/4] RUN npm ci                                                                                              0.0s
=> CACHED [builder 2/4] COPY --from=deps /app/node_modules ./node_modules                                                    0.0s
=> [builder 3/4] COPY . .                                                                                                    3.6s
=> [builder 4/4] RUN npm run build                                                                                           110.3s
=> CACHED [runner 2/8] RUN addgroup --system --gid 1001 nodejs                                                               0.0s
=> CACHED [runner 3/8] RUN adduser --system --uid 1001 nextjs                                                                0.0s
=> [runner 4/8] COPY --from=builder /app/public ./public                                                                     0.0s
=> [runner 5/8] RUN mkdir .next                                                                                              0.2s
=> [runner 6/8] RUN chown nextjs:nodejs .next                                                                                0.1s
=> [runner 7/8] COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./                                           0.2s
=> [runner 8/8] COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static                                   0.0s
=> exporting to image                                                                                                        0.1s
=> => exporting layers                                                                                                       0.1s
=> => writing image sha256:6977b0e02d5daeabd2cb6f97029f06d0d58c014d8731a71589b301df56c8df8f                                  0.0s
=> => naming to docker.io/garciadiazjaime/nextjs-docker                                                                      0.0s

What's Next?
 View summary of image vulnerabilities and recommendations → docker scout quickview

The docker build command looks for the Dockerfile and executes the instructions within. You’ll see each command’s output in the console. The Docker image is named garciadiazjaime/nextjs-docker (in your case garciadiazjaime will be your docker hub username). After completion, let’s run the following command to view your newly created Docker image:

docker images

REPOSITORY                        TAG       IMAGE ID       CREATED       SIZE
garciadiazjaime/nextjs-docker     latest    56c7adc7e01a   3 minutes ago    156MB


See how the garciadiazjaime/nextjs-docker image name appears (garciadiazjaime is my docker hub username, you should see your Docker Hub username). Similar to git, Docker enables pushing to a registry and pulling from any machine (environment), ensuring your application runs seamlessly. As long as the machine (environment) has Docker and your image was correctly built, it’s guaranteed to function across various machines (environments), including production.

Step 3: Run a Docker container

The subsequent step involves running a container after creating the image. In a way similar to the Git analogy, a Docker image, akin to a codebase, doesn’t inherently execute anything. You need an additional command to initiate a process. While for the codebase you might use “npm run dev,” in Docker, the command to commence the process is as follows:

Note: make sure to stop npm run dev, since it runs on the same port 3000.

docker run -d -p 3000:3000 garciadiazjaime/nextjs-docker

WARNING: The requested image's platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested

We are communicating a couple of instructions here:

  • Run a container using the garciadiazjaime/nextjs-docker image.
  • Run the container in the background using the -d flag.
  • Map the container’s port 3000 to the machine’s port 3000.

Please note that the output contains a warning message, which is expected. The warning is related to the first line of the Dockerfile.

FROM --platform=linux/amd64 node:20-alpine AS base

The –platform=linux/amd64 flag is crucial as it specifies the operating system expected for the image to run on. The warning occurs because the Docker image was built for Linux, which is the common operating system for production and similar environments. Although my machine uses OSX, the container still runs without any issues.

If you refresh your browser at http://localhost:3000/, you should observe the same outcome as in step one (Image 1), but this time, the response is from the Docker instance. This is significant because containerizing the application enables it to be deployed to any infrastructure that supports Docker (making deployment easier).

You can also check the Docker container by running the following command:

docker ps

CONTAINER ID   IMAGE                           COMMAND                  CREATED         STATUS         PORTS                    NAMES
ead64e522b9e   garciadiazjaime/nextjs-docker   "docker-entrypoint.s…"   5 minutes ago   Up 5 minutes>3000/tcp   wonderful_shtern

If the application isn’t visible on the browser or if the previous output doesn’t resemble what was expected, it’s time to troubleshoot steps 2 and 3. Feel free to reach out to me for assistance.

You can stop the Docker instance by running:

docker stop ead64e522b9e

Notice how the CONTAINER ID from the output of the docker ps command is used in the previous stop command.

Step 4: Create a Docker Hub Account

If you already have a Docker Hub Account, you can jump to step five.

Docker and Git share a similar approach. In step two, a Docker image was created, and in the previous step, it was confirmed that the image successfully initiated an instance, indicating readiness for registry pushing.

If you don’t have a Docker Hub account yet, navigate to https://hub.docker.com/ and create an account. After logging in, you should see a screen similar to this:

Fig 4: Docker Hub Profile

You won’t find the entry for garciadiazjaime/nextjs-docker in your repository list, but it will be added in the next step. Naturally, it will display your username instead of mine.

Step 5: Push Docker image

To begin pushing an image, you first need to create a repository. On the Docker Hub page, you’ll notice a blue button at the top labeled Create repository. Clicking this will open the Create Repository form. The only field you’re required to fill in is Repository Name. Let’s name it nextjs-docker. Your form will resemble this:

Fig 5: Docker Hub repository creation form.

Note: In your case, Namespace will be your username.

Go ahead and click create. This will take you to the repository page, which should look like this:

Fig 6: Docker Hub repository page.

The crucial part here is the Docker commands section, where you’ll find the following command, let’s copy the command and run it:


docker push garciadiazjaime/nextjs-docker

Using default tag: latest
The push refers to repository [docker.io/garciadiazjaime/nextjs-docker]
3c3f4c697a79: Pushed
0eb26abf9be7: Pushed
ff1ed8259e00: Pushed
75e6021a334c: Pushed
5c6d72162836: Pushed
1d5a5a6a0338: Layer already exists
5c57661fee69: Layer already exists
ae9fc9f2ae41: Layer already exists
e9e57e62cbd7: Layer already exists
abcaa4b7151d: Layer already exists
93043d250b57: Layer already exists
cc2447e1835a: Layer already exists
latest: digest: sha256:41b74907bb0bd3cb7697c7db437794586f9390e71753fbc8aca36035494a68bf size: 2825

Please note: garciadiazjaime is my Docker Hub username. In your case, it will be different. Ensure you use your own username.

You might have noticed that the command doesn’t include :tagname as shown on the Docker Hub page. Although using tags is a good practice, it’s not necessary for this tutorial.

And that’s it! You’ve just successfully pushed your first Docker image ? !

Counting Sheep with JavaScript and ReactJS

iJS New York Program Highlight

Step 6: AWS Account

AWS provides several deployment options for our React (Next.js) application. In this step, we’ll be focusing on the following architecture:

Fig. 7: AWS Architecture for React Application

There are other layers in the diagram, but to simplify, when a user navigates to your domain in a browser, it resolves to a page hosted in AWS. AWS includes a load balancer that directs traffic to the ECS Cluster where the nextjs-docker container operates. This container processes the request and generates the response, similar to the hello world message you encountered in previous steps.

For this tutorial, I’ll utilize the AWS graphical interface to complete the deployment. However, in real scenarios, deploying programmatically, often with Terraform, is a common practice.

Let’s proceed by creating an AWS account. Note that most AWS services have associated fees, so any resources created during this tutorial will be promptly deleted to prevent incurring charges.

I won’t delve into the specifics of creating an AWS account, as numerous tutorials cover this process extensively. The crucial part is having an active AWS account that allows the creation of resources. Rest assured, there won’t be charges for the resources utilized in this tutorial as they’ll be promptly removed.

Step 7: AWS Elastic Container Service ECS

Once logged in, search for ECS and select the entry labeled Elastic Container Service.

Fig. 8. AWS Elastic Container Service Selection

Once inside the Elastic Container Service dashboard, there should be a toggle to switch to the “classic” UI. Find this toggle to revert to the previous interface.


Fig. 9: AWS Elastic Container Service Classic Toggle

Once switched to the classic UI, the page should look like this:

Fig. 10: AWS Elastic Container Service Classic UI

Let’s proceed by clicking Get Started; this action will bring up the next view:

Fig. 11: AWS Elastic Container Service Cluster Creation

Click on Custom, then select the Configure button. This action will prompt the next modal to appear.

Fig. 12: AWS Elastic Container Service Configuration

Notice how the following fields were filled in:

Container name*: nextjs-docker. Image*: garciadiazjaime/nextjs-docker. Memory Limits: 512. Port mappings: 3000.

The container name can be any value; in this instance, I’ve retained the same name as the docker image.

Regarding the Image, it’s crucial that this matches your docker repository. You’ll need to replace garciadiazjaime with your username. Note: Although my docker repository is public, you should use your own for this tutorial to deploy your React application.

Building Scalable UIs with Component-Driven Micro-Frontends

iJS New York 2024 React Track Highlight

Memory Limit doesn’t matter much for now, so let’s stick with a basic value: 512MB.

Regarding port mapping: In Step 2, the Dockerfile defines port 3000, indicating that the React application (nextjs) runs on port 3000.

Now, click Update. This will close the modal and update the previous page, which should now look like this:

Fig. 13: AWS Elastic Container Service Custom Set Up

This page provides a summary of the upcoming creations. If everything appears correct, click on Next. On the subsequent page, it’s crucial to include a load balancer as mentioned in Step Six. The load balancer routes traffic to the container. Click on Application Load Balancer (ALB), which will display port 3000, the port configured for both the Web Application and the Docker instance. If everything seems correct, proceed by clicking Next.

Fig. 14: AWS Elastic Container Service and Application Load Balancer

The following page creates the cluster, as indicated in the Architecture Diagram. The cluster offers scalability. Presently, only one container will be running, but the cluster configuration can accommodate more containers or perform tasks like auto-scaling.

Proceed by changing the Cluster name to nextjs-docker. This field accepts any string, so you can input any desired name. Afterward, click Next.

Fig. 15: AWS Elastic Container Service Cluster Name

The final step presents the “Review Page,” outlining the Elastic Container Service configuration, which includes the:

  • Docker image.
  • Port.
  • Load balancer.
  • Cluster details.

Notably, AWS provides an “Elastic Container Registry” to host Docker images. While it’s available, it isn’t entirely free. Thus, I opted for Docker Hub. Luckily, AWS recognizes the image garciadiazjaime/nextjs-docker without explicitly stating it’s hosted on Docker Hub. As you’ll confirm in the next step, the setup still functions seamlessly.

If everything appears correct, proceed by clicking Create.

Fig. 16: AWS Elastic Container Service Review

After clicking Create, you will land on the Launch Status page, displaying the ongoing creation progress of the components. Currently, there are approximately 10 tasks pending completion. It usually takes a few minutes for the process to finalize. Once finished, you should see a confirmation similar to what’s shown here:

Fig. 17: AWS Elastic Container Service Launch Status Completed.

Great work! You’ve successfully deployed your React application on AWS. This configuration closely mirrors real-world scenarios. In enterprise settings, using tools like Terraform to achieve the same programmatically is common. Developers often prefer working with code rather than UI. Nevertheless, for this tutorial, using the UI suffices, delivering a similar end result.

In Image 17, the one above, upon completion of all ten tasks, you’ll notice multiple clickable links. Let’s click on the last link labeled Load balancer, leading you to a page similar to this one:

Fig. 18: AWS Elastic Container Service Load Balancer DNS

Look at the image above, Image 18, and locate the Two pages icon. Click on it to copy the DNS name. This DNS name is crucial as it represents the URL where your application resides. Once you’ve copied it, paste the URL in a new tab and add port :3000 at the end; it’s essential for the application to function correctly. After adding the port, hit enter, and voila! You should now see your React application hosted on AWS.

Fig. 19: AWS Elastic Container Service Hello World

Feel free to explore the UI and examine the various aspects that have been created:

  • Elastic Container Service
  • Cluster
  • Task
  • Group
  • Load Balancer

When you’re finished exploring, let’s delete these resources to avoid incurring fees from AWS. Follow the same steps as shown in Image 8:

  • In the search bar, type: ecs.
  • Click the first entry: Elastic Container Service.
  • Select the cluster named: nextjs-docker.
  • Click: Delete Cluster.
  • Type in Delete me.
  • Finally, click: Delete.

Fig. 20: AWS Elastic Container Service Delete Resources

Once you are done, you will see the message: “Deleted cluster nextjs-docker successfully”.

Let’s summarize the steps we took:

  1. Created a React application using nextjs with basic implementation.
  2. Developed a Docker image that installs project dependencies and runs the server on port 3000.
  3. Set up a Docker Hub account and repository to host the Docker image.
  4. Created an AWS account and deployed an Elastic Container Service using the Docker image from Docker Hub, along with a load balancer handling traffic on port 3000.
  5. Accessed the React Hello World application hosted on AWS after the resources were successfully created.
  6. Deleted the AWS resources to prevent incurring fees.

In the enterprise environment, tools like Terraform are commonly used. However, understanding the manual process and becoming familiar with the AWS UI is beneficial, as the end result remains the same.

Get in touch

You can find the whole code on GitHub.

I would love to hear your feedback and if you have any questions please send me a line, I’ll be more than happy to answer them.

Special thanks to my friend Alex, who helped me proofread.

Sign up for the iJS newsletter and stay tuned to the latest JavaScript news!



JavaScript Practices & Tools

DevOps, Testing, Performance, Toolchain & SEO


Best-Practises with Angular

General Web Development

Broader web development topics


All about Node.js


From Basic concepts to unidirectional data flows