How I Dockerized my Snake game
A few months ago, I wrote about how I built a React-based Snake game. The game can be played at http://sean-snake.netlify.com/.
You may have noticed that I rely on Netlify to handle the heavy lifting of hosting the game and making it available to everyone with internet access. All I had to do was link the GitHub repository of the project to Netlify, and everything was up and running in the blink of an eye.
At one point, I wondered what I would have done if I wanted to be more involved in the process of deploying the application and hosting it somewhere in the cloud.
The first option that came to mind was creating a virtual machine using AWS, GCP, or something similar. Then, from inside the VM, I could clone the repository, build the app, and run it from there. I had done this before when I needed to host the backend script of my Discord bot (you can read more about it here). While there was nothing wrong with spinning up a VM instance and running a web application from inside that instance, I wanted to try something different this time.
This time, I decided to Dockerize my application!
Write the Dockerfile
First, I needed to come up with the Dockerfile from which the Docker image would be built. The Dockerfile would have to do the following:
- Pull a Node image
- Copy over all the source code to the working directory inside the image
- Install the dependencies based on the content of
package.json
- Start the application the way I normally start it in my local environment
Below is the content of the Dockerfile.
# Fetching the latest node image on alpine linux
FROM node:alpine AS development
# Declaring env
ENV NODE_ENV development
# Setting up the work directory
WORKDIR /react-app
# Installing dependencies
COPY ./package.json /react-app
RUN npm install
# Copying all the files in our project
COPY . .
# Starting our application
CMD npm run dev
Everything looked good when I built the image
docker build -f Dockerfile -t seanluong/snake:v1 .
Troubleshooting
This was almost correct. When I used docker run
below to start the app:
docker run -d -p 8888:8888 seanluong/snake:v1
and navigated to localhost:8888
, the Snake game was not running.
The reason it didn’t work was that the React app was created using Vite, which means:
- It doesn’t run on port 8888 by default, but on port 5173
- The port is not exposed unless the right flag is set when the app is started
Update the port in Vite config
I updated vite.config.ts
to set the default port as 8888.
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
server: {
port: 8888,
},
})
Update the Dockerfile to expose the port
Next, I updated the Dockerfile so that the command to start the app includes the necessary flag to expose the port.
# <same content as before except the last line>
# Starting our application. The "-- --host" helps exposing the port
CMD npm run dev -- --host
And voilà!
All the changes mentioned here can be found in this commit.
Improvement
One problem with this way of Dockerizing the application is that it is not very efficient in terms of disk space usage. A quick look at the image size told me that it was 465MB in size.
% docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
seanluong/snake v1 7e297431ed1a 23 hours ago 465MB
This shouldn’t come as a surprise. If we look at the content of the Dockerfile, we can see that there is no build step. The source code is simply copied over and run the same way it’s normally run in the local environment without Docker.
One way to reduce the image size is to perform the build step so that all the React code (TypeScript and CSS alike) is bundled into build artifacts that will be served as static assets from a web server (like nginx
). This will be the topic for another post.