Dockerizing React Application Built with Vite: A Simple Guide
This article will cover how you can run your React application created using Vite inside a Docker container.
This article assumes you have some basic familiarity with JavaScript, ES6 notations, Node Package Manager(npm), and Docker.
First of all, what in the world is Vite? Vite is a build tool that aims to provide a faster and leaner development experience for modern web projects. It consists of two major parts:
- A dev server that provides rich feature enhancements over native ES modules, e.g. Hot Module Replacement (HMR).
- A build command that bundles your code with Rollup, pre-configured to output highly optimized static assets for production.
You can learn more about Vite from its official website. https://vitejs.dev
In order for our Vite + React application to work properly, you will require a node version that is at least 16 or higher and a relevant npm version.
You can install Vite globally using the below command
npm install -g vite
(Optional) Now, before we start with our React application, let’s set up a workspace folder so that all our projects reside in a single place. Open up the terminal and go to the root level directory, then run the following commands.
mkdir workspace && cd workspace
mkdir react_projects && cd react_projects
Let’s create our React project using Vite. Run the following commands in the terminal.
npm create vite@latest
Then we will be asked to enter the project and package name, framework, and variant. Here we will name our project as sampleProject and package name as sampleproject. The framework will be React and the variant will be javascript; you can select typescript as well. Note that the folder name in the below image is different from the one we created in the above step. This is because I already had that created. This should not be an issue :)
Now as it says, let's move into our project, and for a better development experience, let’s open our application in VSCode.
The folder structure should look similar to that in the above image. Now, when we create a react application with vite, we should see a file named vite.config.js. This is the file where all the vite configurations are defined. We’ll have a look into it later in the article.
Now let’s run npm install
.
npm install
Now we should see the node_modules folder along with the package-lock.json file. Now start the development environment by running npm run dev.
npm run dev
You should see something similar to that in the above image. The server is up and running on port 5173 and you can access it at http://localhost:5173
Now, at this point, we have a pretty basic React application built with Vite running on our local host at port 5173. We can choose to make some changes in the vite.config.js file for better control over our application.
Let’s have a look at the vite.config.js file.
The Vite configuration is basically an object with different keys such as plugins, server, build, preview, optimizeDeps, worker, and more.
To get a deep dive at the configuration options, you can refer to the official documentation, Config.
For the scope of this article, we will only cover the necessary config options. Feel free to copy and paste the below config options and you should be good to go.
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
base: "/",
plugins: [react()],
preview: {
port: 8080,
strictPort: true,
},
server: {
port: 8080,
strictPort: true,
host: true,
origin: "http://0.0.0.0:8080",
},
});
- base: Base public path when served in development or production.
- plugins: Array of plugins to use.
- preview: An object for the Build preview options
- server: An object for the Server options
- port: Specify server port. Note if the port is already being used, Vite will automatically try the next available port so this may not be the actual port the server ends up listening on.
- strictPort: Set to
true
to exit if the port is already in use, instead of automatically trying the next available port. - host: Specify which IP addresses the server should listen on. Set this to
0.0.0.0
ortrue
to listen to all addresses, including LAN and public addresses. - origin: Defines the origin of the generated asset URLs during development.
Now, if we try to run our application, it will run on port 8080.
So far, so good. We can also create a production build of our application by running the following command
npm run build
It will create a folder called dist.
We can then either use npm run preview
or serve to serve our build.
npm run preview
or
npm install serve -g
serve -s dist
Now, let’s get to the interesting part; Dockerizing the application. In order to dockerize, we must have docker installed. We can check if docker is installed or not by running the command docker
in the terminal.
If it shows something similar to the above image, it means that docker is installed. Also don’t forget to start the Docker Desktop application if you are using one.
Let's add a Dockerfile to the application. Feel free to copy and paste the below Dockerfile into the application.
FROM node:18-alpine
WORKDIR /app
COPY package.json .
RUN npm install
COPY . .
EXPOSE 8080
CMD [ "npm", "run", "dev" ]
This should be good enough to run our development environment. But for serving the build, we need to change it a bit(look for the newer version).
# This is the older version!
FROM node:18-alpine
WORKDIR /app
COPY package.json .
RUN npm install
COPY . .
RUN npm run build
EXPOSE 8080
CMD [ "npm", "run", "preview" ]
# This is the newer version
FROM node:18-alpine
WORKDIR /app
COPY package.json .
RUN npm install
RUN npm i -g serve
COPY . .
RUN npm run build
EXPOSE 3000
CMD [ "serve", "-s", "dist" ]
Let’s understand the Dockerfile, step by step. Each step in a Dockerfile is a separate layer that gets built during the image build phase.
FROM node:18-alpine
Firstly, we define the base image on top of which our application will run, Node in this case.
WORKDIR /app
Then we define the WORKDIR; which is the working directory of the docker container at any given time.
COPY package.json .
Then we copy our package.json file from our local system to the docker image.
RUN npm install
Then we run npm install
inside the docker image to install all our dependencies.
RUN npm i -g serve
Then we install serve. It helps you serve a static site, single page application or just a static file.
COPY . .
Then we copy the rest of the files into the docker image.
RUN npm run build
Finally, we ran npm run build
to create a production build of our application.
EXPOSE 3000
The step where we define EXPOSE 3000
is a convention and a good practice that declares on which port the application “should” run and not must run. We can have a different port exposed in the Dockerfile and can use a totally different port while running the image.
CMD [ "serve", "-s", "dist" ]
The last command serve -s dist
runs only when the container spins up. It is not part of the Image creation process.
Before adding a docker-compose file, let’s try to build a docker image from this Dockerfile. In order to build an image from a Dockerfile, run the following command
docker build . -t "sample-project:v1.0"
The above command does two things.
- Builds a docker image from the current context;
.
- Tags the image with the name “sample-project” and version “v1”
We can now check the list of available docker images on our local system by running the following command
docker images
Great! Let’s now try running this image; which is now called a container. Run the following command to spin up a container based on the image “sample-project:v1.0”
docker run -p 3000:3000 sample-project:v1.0
Now let’s check our localhost:3000, and there we go! We have a Vite + React application running inside a docker container.
If we want to perform some other operations in the terminal, currently we cannot do it because currently the application is sort of running in an interactive terminal. In order to run it in a detached state, simply add a -d
flag before the port forwarding option.
docker run -dp 3000:3000 sample-project:v1.0
We will get an ID in return, which is the ID of the running container. We can verify it by checking the list of all the running containers by running the following command
docker ps
To stop a running container, we can use the docker stop <ID>
command.
To delete a container, run docker rm <ID>
.
To delete an image, run the command docker rmi <ImageID>
.
To use docker compose, you can add a docker-compose.yml
file. Here’s a sample compose file
version: '3.4'
services:
sampleproject:
image: sampleproject
build:
context: .
dockerfile: ./Dockerfile
environment:
NODE_ENV: production
ports:
- 3000:3000
You can now run the same application using docker-compose. Run the command docker compose up
and the output should be somewhat similar to this
And that’s a wrap. We have covered so many things in this article. I hope you find this article helpful. If you liked it, don’t forget to give it an Applaud. Also do share it with your friends and connections.
You can follow me to read more such content, I post every week. You can also follow me on LinkedIn at Dhruv Patel