DEV Community

Agnar
Agnar

Posted on

Dockerise a Waku React project

In this post, I will describe how to containerise a React project that was built with Waku and React Server Components. I will not build the app here but focus on creating the image and container.

Prerequisites

Make sure you have Docker and Node.js installed on your machine. I was using Node v18.19.0 and Docker v24.0.5. I built my app with Typescript v5.3, Tailwind CSS v3.4, shadcn-ui and Waku v0.20.

What you really want to know

For those that only want the meat and potatoes.

Let's take a look at Dockerfile.

FROM node:18-alpine as builder

WORKDIR /app

# import dependencies
COPY package.json .
COPY package-lock.json .

RUN npm run install

COPY public/ public/
COPY src/ src/

# UI library/tools/TS config files
COPY components.json .
COPY tailwind.config.js .
COPY postcss.config.js .
COPY tsconfig.json .

RUN npm run build

# Check if build succeeded
RUN [ -d "/app/dist" ] || exit 1

EXPOSE 5000

#start app
CMD ["npm", "run", "start"]
Enter fullscreen mode Exit fullscreen mode

I also used a .dockerignore file like so:

/node_modules
.vscode
Enter fullscreen mode Exit fullscreen mode

Then I used a docker-compose.yml file to build the container, like so:

version: '3.8'

services:
  frontend:
    build:
      context: .
      dockerfile: ./Dockerfile
    container_name: restaurants-fe
    image: restaurantsapp
    working_dir: /app
    environment:
      NODE_ENV: production
    ports:
      - 5000:8080
Enter fullscreen mode Exit fullscreen mode

Finally, I built the container using the following command:

docker-compose up -d --build

Explanation

If you want a bit more information, here are some details on the setup.

Let's start with Dockerfile. I set it up in a way that it uses Docker's multi-stage build paradigm, in which different layers of the process are cached and reused on subsequent builds if the underlying files have not changed since the previous build. Here's what the commands do:

- FROM: use a Node v18 base image to scaffold the build process
- WORKDIR: set a dir path where all the following commands take place
- COPY: copy dependency info in the working dir (signified by the **dot** at the end)
- RUN install: install project dependencies
- COPY public/src: copy other assets (images, jsx files etc.)
- COPY config: copy config files needed for tailwind and shadcn
- RUN build: build the app with production settings
- EXPOSE: make a port available from the container
- CMD: run the start command
Enter fullscreen mode Exit fullscreen mode

If you wish, you can include a health check command at the end of the Dockerfile, which will stop the container if the app is down. Something like this:

HEALTHCHECK --interval=30s CMD wget -qO- http://localhost:5000 || exit 1

Now a quick tour of docker-compose.yml. Of course, this is not necessary for a single container, but I may add more parts to the app in the future and then I can simply add new services to this file. In describing the frontend service:

  • I specify the build context ( cwd ) and the dockerfile path.
  • I add a container_name, image name, and working dir path for good measure
  • I specify any environment variables needed
  • I specify port forwarding from the container to the local machine (I used port:5000, but Waku runs on port:8080 by default. You can use that one too, of course)

Finally, I build the container and start it up with the command:

docker-compose up -d --build

where the -d flag starts the container in the background.

With that, the container should be ready to be deployed to a cloud environment. I leave the topics of 'Docker hub' and 'Deploying Docker containers' to your Googleing skills.

A word about the Waku app

To start building a waku app, take a look at all the examples and starters on Waku's Github. Here I will provide only minimal info for context and potential troubleshooting. The app is a simple website showing info on various restaurants' menus in the area.

This is my project directory structure.


Screenshot of my project's git repo folder structure



My waku app's file tree (essentials shown only)


All other scaffolding code comes from a starter example or the basic template installed with create waku@latest.

In the off-chance that you are building with waku v0.19, I have the alternatives as well. Here is the folder structure.


Screenshot of my project's git repo folder structure, when built with waku version 0.19



My waku app's (waku v0.19) file tree (essentials only)


And this is the entries.tsx file:

import { createPages } from 'waku';
import React from 'react'

import { RootLayout } from './templates/root-layout.js';
import { HomePage } from './templates/home-page.js';
import { AboutPage } from './templates/about-page.js';

export default createPages(async ({ createPage, createLayout }) => {
  createLayout({
    render: 'static',
    path: '/',
    component: RootLayout,
  });

  createPage({
    render: 'dynamic',
    path: '/',
    component: HomePage,
  });

  createPage({
    render: 'static',
    path: '/about',
    component: AboutPage,
  });
});
Enter fullscreen mode Exit fullscreen mode

All other scaffolding code originates just as described above.

What I could not do

In order to decrease the size of the resulting container, I also tried to create an Apache server in the container and copy the static files into its base folder. Similar to what's in this article. I did the following:

.... previous commands ....

EXPOSE 5000

FROM httpd:alpine

WORKDIR /usr/local/apache2/htdocs/

COPY --from=builder /app/dist .

COPY --from=builder /app/dist/public/index.html .
Enter fullscreen mode Exit fullscreen mode

These instructions copy the static JS files and assets from the /dist folder of the app into the root folder of the Apache server. It expects that the index.html file is in this directory, but Waku puts it inside the public directory, so we copy the index file as well. But then, not surprisingly, the relative paths of files referenced in index.html are wrong :-( .

I have no experience configuring http servers, so if anyone does and would like to help out, it would be much appreciated.

Acknowledgement

This post was written on the shoulders of other great tutorials on Docker and React apps. I recommend them for further reading. These are sources from howtogeeks, freecodecamp, Knowledgehut, Antonio Maccarini on Medium and Dan Murphy on Towards Data Science.

Conclusion

Here I show how to package a React app built with the new and awesome Waku JS framework into a Docker container for easy deployment on various platforms. As is probably evident from the above, I am not very experienced with Docker and this was an excellent learning opportunity for me. If you have any comments, suggestions or (reasonable) critique :-) I would be very happy to hear from you.

Top comments (0)