In this task, we’ll create a Docker container for the Posts web application. This process is similar to dockerizing the API, but with some key differences due to the nature of a front-end application.
Our Posts web application is a single-page application (SPA) built with React.js. An SPA is a web application that loads a single HTML page and dynamically updates that page as the user interacts with the application. This approach provides a more fluid user experience by avoiding full page reloads. If you recall, we have an index.html
file that serves as the entry point for the web application. The entire React application is mounted on this HTML file (usually with a div
element with an id
of root
).
During development, the web application is served by a development server that provides hot reloading and other conveniences. However, in production, the web application is typically built into static files (HTML, CSS, and JavaScript) that can be served by static hosting services like GitHub Pages, Netlify, or AWS S3. We “build” the application to create these static files, which are then served by GitHub Pages, or similar services.
Single-page applications (SPAs) are typically served as static files. When a user visits the application in a web browser, the browser downloads the HTML, CSS, and JavaScript files that make up the application. These files are then executed in the browser, allowing the application to run entirely on the client-side.
Under the hood, a static hosting service like GitHub Pages or Netlify serves these static files over HTTP using a web server like Nginx or Apache. This is similar to how we’ll serve our web application in a Docker container.
Nginx is a popular open-source web server that can also be used as a reverse proxy, load balancer, and HTTP cache. It is known for its high performance, stability, and low resource consumption. Nginx is commonly used to serve static files and as a reverse proxy for dynamic content.
In our case, we’ll use Nginx to serve the static files of our web application. We’ll create a Docker container that runs Nginx and serves the built web application files.
Create an nginx.conf
file in the web
directory with the following content:
This configuration tells Nginx to serve the static files and handle client-side routing by redirecting all requests to index.html
. Let’s go over this configuration:
listen 80;
: This tells Nginx to listen on port 80, the default HTTP port. This is where the web application will be served. We use port 80 because it is the standard port for HTTP traffic. In contrast, during development, the web application is typically served on a different port (e.g., 5173) by the development server.
server_name localhost;
: This specifies the server name. In this case, we’re using localhost
as the server name. In a production environment, you would use the domain name or IP address of your server.
root /usr/share/nginx/html;
: This sets the root directory where Nginx will look for the static files to serve. By default, Nginx looks for files in /usr/share/nginx/html
.
index index.html;
: This specifies the default file to serve when a directory is requested. In this case, Nginx will serve index.html
by default.
location / { ... }
: This block handles the client-side routing for SPAs. The try_files
directive checks if the requested file exists. If it doesn’t, it serves index.html
. This is important for SPAs because the client-side router (e.g., Nanostore Router) handles routing on the client side. Without this configuration, the web server would return a 404 error for routes that are handled by the client-side router.
vite.config.js
to set the Base URLIn the web/vite.config.js
file, set the base
option to /
:
This tells Vite to use /
as the base URL when generating the HTML file. This is important for client-side routing to work correctly in the production build.
.dockerignore
FileCopy the .dockerignore
file from the api
directory to the web
directory:
You can adjust this as needed for the web application. For example, there are no *.db
files to ignore in the web application, so you can remove that line from the .dockerignore
file in the web
directory.
Now, let’s create a Dockerfile for the web application:
web
directory in your terminal.Dockerfile
(no file extension).Dockerfile
:This Dockerfile uses a multi-stage build process:
The first stage (build stage) uses Node.js to build the application: It sets up the working directory, copies the package files, installs pnpm and dependencies, copies the rest of the code, and builds the application. This stage is similar to the API Dockerfile, with the exception of running the application at the end.
The second stage (production stage) uses Nginx to serve the built application:
FROM nginx:alpine
: This uses the official Nginx image as the base image.COPY --from=build /app/dist /usr/share/nginx/html
: This copies the built assets from the build stage to the Nginx image. The dist
directory contains the static files generated by Vite.COPY nginx.conf /etc/nginx/conf.d/default.conf
: This copies the Nginx configuration file we created earlier to the Nginx image.EXPOSE 80
: This exposes port 80, the default HTTP port. This is where Nginx will listen for incoming requests. Note that this is only a declaration and does not actually publish the port. The nginx.conf
file specifies that Nginx should listen on port 80.CMD ["nginx", "-g", "daemon off;"]
: This starts Nginx in the foreground. The -g "daemon off;"
flag tells Nginx to run in the foreground rather than as a daemon. This is necessary for Docker containers because Docker expects the main process to run in the foreground. If Nginx ran as a daemon, the container would exit immediately after starting.Now, let’s build and test our Docker image:
Open a terminal window and navigate to the web
directory.
Build the Docker image:
This command builds the Docker image using the Dockerfile
in the current directory (.
) and tags it with the name posts-web
. You can replace posts-web
with any name you prefer. Moreover, you can override the VITE_API_URL
build argument by passing it as a flag to the docker build
command.
Run the Docker container:
This command runs the container, mapping port 8080 on the host to port 80 in the container. The -d
flag runs the container in detached mode (in the background), and the --name
flag gives the container a name (posts-web-container
). There is no significance to the port mapping; you can use any available port on your host machine. For example, you could use -p 5173:80
to map port 5173 on the host to port 80 in the container. This way, you can access the web application at http://localhost:5173
similar to the development server.
To test the web application, first run the API server locally by running pnpm dev
in the api
directory. Then, open a web browser and navigate to http://localhost:8080
. You should see the Posts web application.
Stop and remove the container:
You’ve now successfully dockerized both the API and web application components of the Posts app. In the next task, we’ll create a Docker Compose file to run both containers together, simulating a complete deployment of the application.