This documentation provides a step-by-step guide to prepare a Single Page Application (SPA) that was built using ReactJS
for deploying it in any cloud using LocalOps.
Prerequisites
To follow this tutorial, you will need the following:
- Node.js, version 20.15.1 at the time of writing.
- npm, as a package manager for installing and maintaining dependencies. This usually comes bundled with Node.js.
- A foundational knowledge of React.
- Vite for a development server and building optimized static assets for production.
- Nginx to serve static assets and also act as a reverse proxy server.
- Docker to build standalone containers to serve your production app.
This guide assumes that you have basic knowledge of the above-mentioned technologies and tools. If you are not
familiar with any of them, it is recommended to review their official documentation and tutorials to get up to speed
before proceeding with this guide.
Scaffolding the React app
Vite offers a CLI tool that makes the scaffolding process much easier. Run the following command:
Follow the prompts to create your first app. Refer to the
Vite guide to learn more about scaffolding the app.
Once the application is ready, navigate to the directory and install all the dependencies:
cd vite-project # replace with the name of the project you created
npm install
After the dependencies are installed, you can start the dev server by running:
This usually starts the server on port 5173. Go to http://localhost:5173 to see your app.
The dev server is enabled with HMR, so it hot reloads the
application when you make changes.
Building Your Production App
When it is time to deploy your app for production, simply run:
This command builds a highly optimized static bundle. By default, it uses <root>/index.html as the build entry point
and produces an application bundle that is suitable to be served over a static hosting service. You can read more about
building for production in the Vite guide.
The output is found in the <root>/dist directory unless configured to a different path.
Once built, you can preview the production build using npm run preview to start a production-like local web server.
It is important to note that vite preview is intended for previewing the build locally and not meant as a production
server.
Configuring nginx
Once the application is built for production, we need a static web server to serve those files. For that purpose, we are
using [Nginx]. Nginx is an open-source HTTP and reverse proxy server.
server {
listen ${PORT};
server_name _;
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
}
# skip this block if you don't have a backend yet
location /api/ {
proxy_pass http://{BACKEND_HOST}:${BACKEND_PORT};
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
Create this file nginx.conf.template in the root of your project. We wonβt be using this template now; weβll use it to
run the Nginx server inside the Docker container.
Note two important blocks in this configuration:
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
}
This block serves the index.html from the files we built using Ember, stored in the /usr/share/nginx/html folder. We
will copy the assets to this folder while building the Docker application.
And then the API block:
location /api/ {
proxy_pass http://{BACKEND_HOST}:${BACKEND_PORT};
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
You might want to skip the API block if you donβt have a backend yet; otherwise, nginx will throw an error for an
invalid host on the backend URL.
A typical non-static web application usually will have a backend, and we make AJAX requests using
fetch or libraries like
axios to get/send data to retrieve/store the application state.
Assume that the site we built is being served by nginx via the domain example.com. Whenever an API call is made to
example.com/api, the nginx server acts as a reverse proxy server and forwards the request to the backend server.
You can use reverse proxying to navigate the traffic that is inbound to nginx. The reverse proxy server also acts as a
mask, hiding the real backend from the outside world.
Create the Docker Image
Dockerizing makes the app run anywhere, agnostic of the platform. As long as Docker is installed, whether itβs Windows,
Mac, or Linux, it can run with the same behavior.
Before creating the Dockerfile, letβs create a .dockerignore file and add the contents that should not be copied over
to the Docker file system.
Dockerfile
.dockerignore
node_modules
npm-debug.log
README.md
.git
Read more about .dockerignore here.
Now, create a Dockerfile. The Dockerfile is a text file that contains
the instructions for Docker to build the image.
The Dockerfile is posted for reference with steps to create the production image. We leverage Dockerβs
Multi-Stage Builds to create a smaller-sized image for running
the production server.
# Stage 1: Base
# This stage is the first stage
FROM node:20-alpine AS base
# Switch to the app directory,
# this will be our app's root directory inside the container
WORKDIR /app
# Copy the manifest and lock file to the root
# Note: ./ refers to /app, since we switched context in the above step
COPY package.json package-lock.json* ./
# Install the dependencies
RUN npm ci
# Set the NODE_ENV to production
# to configure the application to build/run under production mode
ENV NODE_ENV=production
# Copy all the files from our machine to the container
COPY . .
# Run the build to create production assets
RUN npm run build
# Stage 2: Production
# Now we build a new stage from scratch to run the nginx server
# This won't include any images from the previous steps
FROM nginx:alpine AS production
# The port you'd like to use for Nginx
ENV PORT=3000
# The URL for the backend service. Skip if irrelevant to you.
ENV BACKEND_HOST=host.docker.internal
# Port for the backend. Skip if irrelevant to you.
ENV BACKEND_PORT=3001
# Copy the template we created earlier for nginx to the etc folder
# note the destination file name should be default.conf.template
# else the variables in the config won't work
COPY nginx.conf.template /etc/nginx/templates/default.conf.template
# Since we started a new stage, we need to copy the build from the
# build step to the nginx's html directory. Note we have configured
# this directory in the nginx config file
COPY --from=base /app/dist /usr/share/nginx/html
# Expose the port the nginx is running to the outside world
EXPOSE ${PORT}
# Start the nginx server
CMD ["nginx", "-g", "daemon off;"]
Now you can build and run the Docker image. To build the Docker image:
docker build --platform linux/amd64 -t react-app:latest .
This command builds the react-app image for the platform linux/amd64 and tags it as latest.
If you are locally testing your application, you can skip the platform key to build the images.
# only for testing in a local machine
docker build -t react-app:latest .
Run the Docker Image
Letβs run the Docker container using the image created of the React application with the command below.
docker run \
-it \
--rm \
--name react-app \
-e PORT=3000 \
-e BACKEND_PORT=3001 \
-e BACKEND_HOST=host.docker.internal \
-d \
-p 3000:3000 \
react-app
-it: enables interactivity with TTY.
--rm: tells the Docker Daemon to clean up the container and remove the file system after the container exits.
--name react-app: Name of the container react-app.
-e PORT=3000: Sets the environment variable PORT in Docker to 3000.
-e BACKEND_PORT=3001: Sets the environment variable BACKEND_PORT in Docker to 3001.
-e BACKEND_HOST=host.docker.internal: Sets the environment variable BACKEND_HOST in Docker to
host.docker.internal.
-d: Runs the container in detached (background) mode. You can skip the flag to see the logs directly in your
terminal window.
-p 3000:3000: Maps port 3000 on your host to port 3000 in the container.
react-app at the end is the name of the image.
After running the command, visit http://localhost:3000 to see the React application running inside the Docker
container.
Hurray π. Now we have created and packaged a React app for production use.
Example app
Source: https://github.com/localopsco/js-example-spa
js-example-spa is an example TODO app built with. This project is dockerized and the images are published to Amazon
Elastic Container Registry (ECR).
Done π
You can now commit and push the Dockerfile to your git repo. Create a service now to
point at the git repository and branch name to deploy this image.