Plainionist Become a Better Developer, Deliver Software Faster

My first Docker container

Docker

Even though Docker is popular already for quite a while, I never had an opportunity to give it a try. Until recently when I bought a Raspberry PI to host some web applications.

Of course, hosting these web applications directly on the PI would have been simpler and more efficient but then I still wouldn’t have any hands on experience with Docker.

Here is the summary of my first Docker experiment.

The Web Application

The web application consists of a Node.js backend and a Vue.js based Web UI, built with Vite.

Project setup

The backend and the frontend are separate projects because I plan to rewrite the backend in Rust soon.

To make the backend serving the Web UI in production, the Web UI is configured to build directly into the “public” folder of the web server.

import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  build: {
    outDir: '../WebApi/dist/public/'
  },
  plugins: [
    vue(),
  ],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  }
})

Dockerfile

My first working Dockerfile produced a 270 MB image which was an unexpected high overhead for a small Node.js based web application. I did some research, learned about minimal Linux distributions for Raspberry PI and about two stage Docker files and finally came up with the Dockerfile below which produces a 65 MB image.

ARG NODE_VERSION=18.15.0
FROM --platform=linux/arm64/v8 node:${NODE_VERSION}-alpine as build-stage

ENV NODE_ENV development

WORKDIR /usr/src/app

COPY src/price-watch/WebUI/package.json ./WebUI/
RUN npm install --prefix ./WebUI/

COPY src/price-watch/WebApi/package.json ./WebApi/
RUN npm install --prefix ./WebApi/

COPY src/price-watch/WebApi ./WebApi/
COPY src/price-watch/WebUI ./WebUI/

WORKDIR /usr/src/app/WebUI/
RUN npm run build 

WORKDIR /usr/src/app/WebApi/
RUN npm run build


FROM --platform=linux/arm64/v8 alpine as production-stage

RUN apk add --update nodejs

RUN addgroup -S node && adduser -S node -G node
USER node

COPY --from=build-stage /usr/src/app/WebApi/dist /app/
WORKDIR /app/

EXPOSE 8001

CMD node server.js

Build & Deploy

To build a Docker image I have set up a dedicated builder

docker buildx create --name pi-builder --use

and used it to build the image

docker buildx build --platform linux/arm64 -t price-watch:pi . --load

I have saved the image locally

docker save price-watch:pi --output price-watch.tar

and copied it over to my PI. There I loaded into docker

docker load --input price-watch.tar

and successfully started my first Docker container

docker run -d --restart=unless-stopped --name price-watch -p 8001:8001 price-watch:pi

Tags: technologies