Developer Center

How to deploy your React app using Container Registry

How to deploy your React app using Container Registry

Introduction

The advent of cloud-native computing has greatly transformed the landscape of building and distributing software. You have various tools to facilitate everything from integrating and packaging application code to deploying and scaling it. The practice of doing this is known as DevOps, and the concept that sits at the heart of modern-day DevOps is containers.

In this tutorial, you will learn to deploy a React app by creating a Docker image, pushing it to a Container Registry, and deploying it using a simple Droplet server.

Prerequisites

Before proceeding, let’s walk through some basic terminology.

What is a Container?

A container is “a standard unit of software that packages up code and all its dependencies so the application runs quickly and reliably from one computing environment to another.”

Imagine a container as a lightweight virtual machine running your application, and you can spin it up/down using a container image.

What is a Container Image?

You cannot talk about containers without talking about container image, which is “a lightweight, standalone, executable package of software that includes everything needed to run an application: code, runtime, system tools, system libraries, and settings.”

A container image becomes a container at runtime.

Containers make it really easy to package, deploy, distribute, and scale application code. They encapsulate your code and all the dependencies required to run it in a neat, multi-platform, easily replicable bundle.

One of the most well-known and widely used ways of containerization is using Docker. You can read about it in this article.

Now, let’s dive into how you actually containerise your application.

Install Docker

You will start off by installing Docker. Here’s a pretty good guide to get it up and running. Once it is set up, check the docker version to ensure it’s running fine.

docker --version
Docker version 26.0.0, build 2ae903e

Create React App

Next, you spin up the React app. You should have your React app on your local machine or a GitHub repository. If you have it on Github, clone the repo to your machine OR create a new sample react app using Vite, by running the following command:

npm create vite@latest react-app -- --template react

This should initialize a sample React app inside the react-app directory. Let’s run the app to ensure it’s working properly.

cd react-app
npm install
npm run dev

Wait for your app to compile. Once it’s done, you should see a message similar to the following.

Output
VITE v5.2.11 ready in 712 ms ➜ Local: http://localhost:5173/ ➜ Network: use --host to expose ➜ press h + enter to show help

And you should be able to view your app in your browser at localhost:5173.

React app deployed

Great, the app is up and running on your machine. Now, all that remains is to package it somehow and get it to production.

Create Dockerfile

Docker helps you containerize and run your application code using Docker/container images. To build such an image you use something called Dockerfile which contains instructions to build the image. This image consists of many read-only layers and each Dockerfile instruction adds a new layer to the image. Each of these layers is stored as a SHA-256 hash. You can learn more about the Docker image constitution here.

So you will create a Dockerfile to build your app image and it will consist of two important steps:

  1. Building the app.
  2. Setting up and running nginx to serve the app.

Let’s go over each one.

Note: In the next two steps, all the instructions will go inside the Dockerfile.

1. Building the app

You will ask Docker to use the latest Node.js image as a base to build your React app. It’s like setting up the foundation for building construction – you need a solid starting point.

FROM node:latest AS builder

Establish the working directory within the container to execute all subsequent commands. Think of it as defining the workspace where your project will come to life.

WORKDIR /app

Now, you will copy your package.json file into the container and run npm install to install all the dependencies listed in the package.json file. This step is crucial for ensuring that your React app has everything it needs to run smoothly.

COPY package.json .
RUN npm install

Next, copy the rest of your project files into the container and run npm run build to compile the app. This step transforms the source code into a production-ready bundle optimized for performance and efficiency and places it under the dist/ directory.

COPY . ./
RUN npm run build

2. Setting up and running nginx to serve the app

Nginx is a popular web server known for its speed and efficiency, making it ideal for serving your React app to the users. You will use the latest nginx image as the base for your server.

Firstly, you will customize nginx’s configuration by replacing its default settings with your own. This ensures that nginx knows how to handle requests and serve the app correctly.

FROM nginx:latest
RUN rm -rf /etc/nginx/conf.d/default.conf
COPY ./nginx/default.conf /etc/nginx/conf.d/default.conf

Then copy the compiled React app from the builder container into the directory where nginx expects to find the files it serves.

This step ensures that nginx can access your app’s files and serve them to visitors.

COPY --from=builder /app/dist /usr/share/nginx/html

Set the working directory within the nginx container to where your React app files are located.

WORKDIR /usr/share/nginx/html

Lastly, start the nginx server with the following command. It tells Docker to launch nginx and keep it running.

CMD ["/bin/bash", "-c", "nginx -g "daemon off;""]

Finally putting everything together, this is what your Dockerfile will look like:

# ------------------------
# Step 1: Build react app
# ------------------------

# Use node:latest as the builder image
FROM node:latest AS builder

# Set the working directory
WORKDIR /app

# Copy package.json and install app dependencies
COPY package.json .
RUN npm install

# Copy other project files and build
COPY . ./
RUN npm run build

# --------------------------------------
# Step 2: Set up nginx to serve the app
# --------------------------------------
# Use nginx:latest as the base image
FROM nginx:latest

# Overwriting nginx config with our own config file
RUN rm -rf /etc/nginx/conf.d/default.conf
COPY ./nginx/default.conf /etc/nginx/conf.d/default.conf

# Copy over the build created in the Step 1
COPY --from=builder /app/dist /usr/share/nginx/html

# Set the working directory
WORKDIR /usr/share/nginx/html

# Start nginx server
CMD ["/bin/bash", "-c", "nginx -g \"daemon off;\""]

So create a new file called Dockerfile in the root directory of your app:

touch Dockerfile

And copy the contents of the Dockerfile we created above inside this file.

You’re almost done. Just missing one crucial piece of the puzzle before you are ready to build the image. If you’re wondering where your nginx config is, you’re right on the money. That’s the vital component you need to complete the picture.

Create nginx Config

To ensure step 2 works correctly, create a directory called nginx at the root of your app and add an empty default.conf file to the same directory.

mkdir nginx
cd nginx && touch default.conf

Copy the following configuration to default.conf:

server {
  listen 80;
  add_header Cache-Control no-cache;
  location / {
    root   /usr/share/nginx/html;
    index  index.html index.htm;
    try_files $uri $uri/ /index.html;
    expires -1;
  }
  error_page   500 502 503 504  /50x.html;
  location = /50x.html {
    root   /usr/share/nginx/html;
  }
}

This asks nginx to start listening on port 80, specifying the files to serve, and configuring some default error based on the status code. You can read this tutorial to understand the nginx configuration file structure and configuration contexts. Also, here’s a really handy UI tool for constructing nginx config files.

With that, you are all set to build and deploy your image.

Build Your Image

Make sure you cd back to the root dir of your app – where the Dockerfile is and run the following:

docker image build -t react-app:v1.0 . --platform linux/amd64

An image is identified by its name and tag, which are specified by the —t flag. So, the name of your image is react-app, and the tag is v1.0. Notice that there’s a --platform flag to specify the architecture for which you build the image. Usually, you can omit this, but sometimes, while building cross-platform images, it’s required so that the image is compatible with the platform it’s intended to run on. You can build the same image for multiple platforms if required.

You should see something similar to the following once the build finishes:

[+] Building 113.9s (17/17) FINISHED
=> [internal] load build definition from Dockerfile
=> => transferring dockerfile: 982B
...
...
=> => writing image sha256:d125b102b094224c82ddb69f9ece98c7161c8660fe72fd5aec57e41a6d72cf2f
=> => naming to docker.io/library/react-app:v1.0

Verify that your image is built successfully. Use the following command:

docker image list
Output
REPOSITORY TAG IMAGE ID CREATED SIZE react-app v1.0 d125b102b094 6 minutes ago 188MB

You should see your app in the list of images, as shown above.

Now that the image is ready, it’s time to prepare it for distribution and deployment. As mentioned earlier, the awesome part about an image is that it is self-sufficient and can be run on any platform that supports Docker.

Naturally, the image must be stored in a centralized location before it can be distributed and deployed.

That’s where a Container Registry comes into the picture.

Push Image to Container Registry

Container Registry is a centralized repertoire of container images. It lets you store container images for rapid deployment. You can push your images to a Container Registry and then pull from anywhere – be it locally, from a Kubernetes cluster, CI/CD pipeline, etc. Let’s look at how to push the image to a Container Registry.

You must set up your Container Registry before you can start pushing images to it. You should use the private Container Registry by DigitalOcean. It is simple, private, and secure. You can choose from multiple tiers, and you can start by simply using the free tier, which offers up to 500MB of storage with a single repository—which should be more than enough to push your app image.

Setting up the DigitalOcean Container Registry is fairly simple and fast. You can follow these instructions to get started.

For this example, you can set up a free-tier registry in the BLR1 region and call it my-container-registry.

container registry

Note: You need to have a DigitalOcean account to use Container Registry, so make sure to sign up if you haven’t already.

The next step is to get the API token to access the registry. You’ll need this later on. Go to the API Tokens page to generate the token.

API token page

Click on Generate New Token to see a form. Fill out the form details and select Full Access. Since you are the only one using this token, it shouldn’t really be a security concern.

Generate New Token

An API token will be generated for you. Make sure to copy this token somewhere safe. Later, you will need it to log in to your registry.

Token Generated

Now let’s log in to the registry. Run the following in your terminal and enter the login credentials:

docker login registry.digitalocean.com
Output
Username: ravish.foo@bar.com Password: Login Succeeded

Now, you are ready to push the image. So, let’s tag it for pushing.

docker tag react-app:v1.0 registry.digitalocean.com/my-container-registry/react-app

And push the image:

docker push registry.digitalocean.com/my-container-registry/react-app
Output
Using default tag: latest The push refers to repository [registry.digitalocean.com/my-container-registry/react-app] 5f70bf18a086: Pushed fafde3127bc5: Pushed 155c640ab606: Pushed 993afd9f06ad: Pushed 9fd54926bcae: Pushed 175aa66db4cc: Pushed e6380a7057a5: Pushed 1db2242fc1fa: Pushed b09347a1aec6: Pushed bbde741e108b: Pushed 52ec5a4316fa: Pushed latest: digest: sha256:227a8c3ede3fc5c81b281228600bec84939f8a4a0cc770fc6e5527b3261e77f4 size: 2608

The image you built now sits inside the Container Registry and should appear on the cloud control panel.

Cloud control Panel

As you can see, the image has been pushed to the registry and is tagged latest. That’s because no tag name was specified while tagging the image for pushing. If no tag is specified, latest is chosen as the default tag name.

You must note that you can tag your Docker image with any identifier you choose. One practical application of this feature is versioning your images.

For instance, let’s say you implement changes to your code and want to associate those changes with a specific release, such as v1.0. By tagging your image with v1.0 and pushing it to your registry, you establish a clear reference point for that release. As you continue to update and release, you can incrementally increase the version numbers (e.g., v1.1, v1.4, v2.0), providing a chronological record of your code releases within your registry. This systematic approach helps maintain a structured history of your app’s development and simplifies tracking and managing different versions over time.

Spin up a Droplet

You have built and pushed your image. Now, you only need to spin up a Droplet and deploy your container using the image.

Follow the guide here to spin up a droplet. The $6 variant should be good; set Ubuntu as the OS of choice. Once you have a Droplet running, you should see it in your control panel.

Create Droplet

Click it open and copy the IP address of your droplet. As the arrow points out, you will find it right under the droplet’s name and details.

Droplet Details

Using the IP address, you will now ssh into the Droplet as root.

ssh root@143.244.130.234
root@143.244.130.234's password:

Based on the authentication method you chose while setting up the Droplet, you may or may not have to provide a password to ssh into your Droplet.

Once you are successfully ssh-ed into your Droplet, you should see a welcome message similar to the root prompt.

Welcome to Ubuntu 23.10 (GNU/Linux 6.5.0-9-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Mon Apr 29 08:07:53 UTC 2024

  System load:  0.32              Processes:             106
  Usage of /:   9.3% of 23.17GB   Users logged in:       0
  Memory usage: 40%               IPv4 address for eth0: 143.244.130.234
  Swap usage:   0%                IPv4 address for eth0: 10.47.0.5

47 updates can be applied immediately.
To see these additional updates run: apt list --upgradable

*** System restart required ***
Last login: Mon Apr 29 08:07:55 2024 from 198.211.111.194
root@ubuntu-s-1vcpu-1gb-blr1-01:~#

You’re now inside the droplet.

Pull Image from the Container Registry

Once you’re inside the droplet, you must pull and run the app image from your registry.

Check if you have Docker on your Droplet:

root@ubuntu-s-1vcpu-1gb-blr1-01:~# docker --version
Docker version 24.0.5, build ced0996

If not, install Docker:

root@ubuntu-s-1vcpu-1gb-blr1-01:~# snap install docker

Once installed, log into the registry again following the same steps as stated earlier:

root@ubuntu-s-1vcpu-1gb-blr1-01:~# docker login registry.digitalocean.com
Username: ravish.foo@bar.com
Password:
Login Succeeded

And pull the image:

root@ubuntu-s-1vcpu-1gb-blr1-01:~# docker pull registry.digitalocean.com/my-container-registry/react-app
Output
Using default tag: latest latest: Pulling from my-container-registry/react-app b0a0cf830b12: Already exists 8ddb1e6cdf34: Already exists 5252b206aac2: Already exists 988b92d96970: Already exists 7102627a7a6e: Already exists 93295add984d: Already exists ebde0aa1d1aa: Already exists 6156e0586e61: Already exists 88bdf71f3911: Pull complete f5524cce8c8a: Pull complete 4f4fb700ef54: Pull complete Digest: sha256:227a8c3ede3fc5c81b281228600bec84939f8a4a0cc770fc6e5527b3261e77f4 Status: Downloaded newer image for registry.digitalocean.com/my-container-registry/react-app:latest registry.digitalocean.com/my-container-registry/react-app:latest

This pulls the image with the latest tag. Remember that you can specify the tag you want to pull. Since the tag name was omitted, docker pulled the latest one by default.

Run the Container

Now you have your container image, which consists of the latest React app code with all of the dependencies required to run it. If you recall, a container image is what becomes a container at runtime. So, let’s run the container using the image you just pulled now.

docker run -dp 80:80 registry.digitalocean.com/my-container-registry/react-app

The -d flag detaches the container and runs it in the background. The -p 80:80 maps port 80 on the host to port 80 on the container, directing traffic to the container’s port.

Port 80 is exposed because it is the standard port for HTTP traffic and where nginx would be listening for incoming requests.

Run the following to make sure your container is up:

root@ubuntu-s-1vcpu-1gb-blr1-01:~# docker ps -a

You should see your container in the list. Check the status - it should say something like “UP 2 seconds”. That means your container is up and running.

Congratulations! Your React app is now live on the internet and anyone can see it.

Type in the IP address of your Droplet in the browser, and you should be able to see and interact with your web app.

App deployed

You can stop the container by running:

docker stop <container_id>

Once stopped, you can also remove the container by running:

docker rm <container_id>

Conclusion

You can go a step further and automate this process – write a bash script to integrate your GitHub so that every commit automatically builds an app image and pushes it to the Container Registry. You can also automate pulling the image from the Registry and deploying it to the cloud, creating a CI/CD pipeline of sorts. Tools like Concourse and GitHub Actions are a more sophisticated rendition of exactly this.

Further, you could use Kubernetes to manage a whole slew of containers to manage large-scale, production-grade services. DigitalOcean has it’s own managed Kubernetes that can be seamlessly integrated with DigitalOcean’s Container Registry and unlock the real potential of container-based cloud-native deployments.

You can read more about it here.

Learn More

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about our products

About the authors
Default avatar

Senior Engineer @DigitalOcean


Default avatar

Sr Technical Writer

Sr. Technical Writer@ DigitalOcean | Medium Top Writers(AI & ChatGPT) | 2M+ monthly views & 34K Subscribers | Ex Cloud Consultant @ AMEX | Ex SRE(DevOps) @ NUTANIX


Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
Leave a comment


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Become a contributor for community

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

DigitalOcean Documentation

Full documentation for every DigitalOcean product.

Resources for startups and SMBs

The Wave has everything you need to know about building a business, from raising funding to marketing your product.

Get our newsletter

Stay up to date by signing up for DigitalOcean’s Infrastructure as a Newsletter.

New accounts only. By submitting your email you agree to our Privacy Policy

The developer cloud

Scale up as you grow — whether you're running one virtual machine or ten thousand.

Get started for free

Sign up and get $200 in credit for your first 60 days with DigitalOcean.*

*This promotional offer applies to new accounts only.