Dockerise a Waku React project
Tags: react, docker, webdev, waku
Published: 2024-03-27 10:20:44

In this post, I will describe how to containerise a React project that was built with [Waku](https://waku.gg/) 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`.

```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"]
```

I also used a `.dockerignore` file like so:

```
/node_modules
.vscode
```

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

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

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](https://docs.docker.com/build/building/multi-stage/) 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

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](https://github.com/dai-shi/waku/tree/main/examples). 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.

<figure>
![Screenshot of my project's git repo folder structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f8umu8e41p0oc0lsafzu.png)
<figcaption>
My waku app's file tree (essentials shown only)
<figcaption/>
<figure/>

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.

<figure>
![Screenshot of my project's git repo folder structure, when built with waku version 0.19](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wg0ofnb4ohdkflfjrwzc.png)
<figcaption>
My waku app's (waku v0.19) file tree (essentials only)
<figcaption/>
<figure/>

And this is the `entries.tsx` file:
```ts
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,
  });
});
```

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](https://www.howtogeek.com/devops/how-to-dockerise-a-react-app/). I did the following:

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

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](https://www.howtogeek.com/devops/how-to-dockerise-a-react-app/), [freecodecamp](https://www.freecodecamp.org/news/how-to-dockerize-a-react-application/), [Knowledgehut](https://www.knowledgehut.com/blog/web-development/how-to-dockerize-react-app), Antonio Maccarini on [Medium](https://medium.com/@antonio.maccarini/dockerize-a-react-application-with-node-js-postgres-and-nginx-124c204029d4) and Dan Murphy on [Towards Data Science](https://towardsdatascience.com/deploying-a-react-nodejs-application-with-docker-part-i-of-ii-910bc6edb46e).

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