Dockerizing React Application Built with Vite: A Simple Guide

Dhruv Patel
8 min readSep 20, 2023

--

This article will cover how you can run your React application created using Vite inside a Docker container.

Docker + React + Vite

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.

Node and npm versions

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 :)

npm create vite@latest

Now as it says, let's move into our project, and for a better development experience, let’s open our application in VSCode.

VSCode image of sampleProject

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
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
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

Vite + React app running on port 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.

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 or true 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.

VSCode Terminal
localhost: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.

npm run build

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
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.

Docker

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 distruns 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”
docker build . -t “sample-project:v1.0”

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
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

docker compose up

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

--

--

Dhruv Patel
Dhruv Patel

Written by Dhruv Patel

Writing about productivity systems and life optimization strategies. Software engineer sharing insights on clean code, architecture, and best practices.

Responses (10)