Dockerize your Meteor App

I was trying to Dockerize my meteor app and felt somebody could benefit. I am using alpine linux and volumes to share the runtime parameters for the meteor app and pm2 to run it. By using 2 stage build process the image size is reduced. I am sure the final image size can be further reduced if pm2 is not used. Please do share if you make this better.

Prereq - You have docker installed in your system

Steps

  • Create a folder to store Dockerfile and build output of meter
  • Use “meteor build” and copy the entire build directory to the newly created folder
  • Create Dockerfile file in this folder and copy the following content to it.
# Pull base image.
FROM mhart/alpine-node:8
# Install build tools to compile native npm modules
RUN apk update \
   && apk upgrade \
   && apk add build-base python bind-tools  libxml2-utils libxslt
# Create app directory
RUN mkdir -p /app
# Copy meteor build bundle
COPY bundle /app
# Build for the image
RUN cd /app/programs/server && npm install --unsafe-perm
FROM keymetrics/pm2:8-alpine
RUN mkdir -p /app
RUN mkdir -p /usr/pm2
COPY --from=0 /usr/bin/node /usr/bin/
COPY --from=0 /usr/lib/libgcc* /usr/lib/libstdc* /usr/lib/
COPY --from=0 /app /app
EXPOSE 3000
CMD ["pm2-runtime", "/usr/pm2/pm2.json"]

  • Build Docker image replacing meteorapp with your appname if you want
docker build -t meteorapp .
  • Now you need to create the pm2.json file for running. Make sure you add or change what is needed for running your app - sample pm2.json below
 {
  "apps": [
    {
      "name": "app1",
      "cwd": "/app",  // do not change as it should be same as defined on image 
      "script": "main.js",
      "env": {
        "NODE_ENV": "production",
        "WORKER_ID": "0",
        "PORT": "3000", // 3000 for now as that is exposed 
        "ROOT_URL": "https://XXX.XXX.XXX",
        "MONGO_URL": "mongodb://xxx.xxx.xx.xxx:27017/meteor",
        "MONGO_OPLOG_URL": "mongodb://xxx.xxx.xx.xxx:27017/local",
        "HTTP_FORWARDED_COUNT": "1",
        "MAIL_URL": "smtp://xxx.xxx.xxx",
        "METEOR_SETTINGS": {
          "public": {
	           
          },    
          "private": {
            
          }
        }
      }
    }
  ]
}

If your build was successful, you can now run it by issuing command (hopefully Mongo server is accessible somewhere)

docker run -p 3000:3000 -v absolutePathOfPM2File:/usr/pm2 -t meteorapp

Hopefully this is useful to somebody.

3 Likes

These lines are extremely suspicious:

COPY --from=0 /usr/bin/node /usr/bin/
COPY --from=0 /usr/lib/libgcc* /usr/lib/libstdc* /usr/lib/
COPY --from=0 /app /app

I see now this is supposed to be copying files from a layer or image. But it’s very unconventional.

Also, I don’t think you should use pm2. It’s making this all unnecessarily obscure. For example, I have no idea where the node logs will go just looking at this. The convention is that the container should just print them to standard out, instead of writing them somewhere in its own ephemeral storage that needs to be rotated out.

If you want a better base image, use phusion:baseimage, and install node using the steps from the bog-standard node images.

2 Likes

Thank you @doctorpangloss lot of good points for me to think and refine .

Some background that led me to this path. I have a meteor app running in production for last 3 years. I had started with MUP but found it was not being maintained as quickly as things were changing. So went back to doing things manually and found it very easy and much better than trying to leave it to a tool. This when I started using PM2 due to some features it was doing out of the box - therefore bias towards PM2. But will drop PM2 as in a Docker it may not give the benefit.

One of the reason I was hesitant to use docker 3years back was the size of image. Even now the size has not come down but my internet uploads speeds have gone up so I am exploring docker now. But coming from old school and with advent of edge computing want to keep the size of docker as small as possible. Alpine boasts itself as having the smallest base-image. But there too deploying a node app - doing ‘npm install’ requires a larger base image that takes care of dependencies. While going through the document of Alpine it is suggested that the app can built using a different image that has the necessary packages to build - After the build only necessary files required to run the app can be copied to more slim base image and this is possible by staging in Docker build process. By this method I was able to reduce the final image by 150MB. I think it can be further reduced if I use the base image from Alpine rather than from keymetrics image.

But thank you for the pointers, will work on the suggestions.

Did you have a look at https://docs.docker.com/develop/develop-images/multistage-build/?

Looks like it’s loading the minimum artefacts required into the final docker container of a multi-stage build
Which, if it works, is very nice

Would you mind putting the code on github? I’d like to star it :slight_smile: And I think it would be something that would get quite some forks, coming back with better solutions or at least feed your discussions.

I’m currently using MUP and think of replacing the current technology stack, which would make it unusable to me. A script, like the one you posted here, would be a great start.

While doing some research about it I stumbled over other implementations in Docker:

How is their solution different from yours? In my opinion everyone is aiming for a minimal container size, right? I mean … if there’s no value having more data than needed, why shouldn’t you keep it out.

3 Likes

Did you guys notice the new project https://github.com/CyCoreSystems/go-meteor ?

1 Like

Not until you posted it here, looks nice.

Just allow me this question …

I see Go more and more approaching the scene when working with Meteor. What was your reason for not using JavaScript to write this tool? I doubt you chose it for performance reasons.

Here is my solution for Dockerizing a Meteor app to a working and light image without pm2.

chneau/meteor:alpine -> https://github.com/chneau/docker-meteor/blob/alpine/Dockerfile

basically, it does this in a 3 step

  • install meteor packages, install npm packages, create bundle
  • intstall npm packages of the bundle
  • set up timezone and envs

So far it works.

2 Likes

Looks good. One of the reason I had used PM2 was to set env variables specially Meteor.Settings. However there are other ways to set it as well.

2 Likes