Hi all
Which docker approach / image would u use for production hosting using latest meteor?
Anyone know if a guide that works with latest meteor?
Best
Jesper
Hi all
Which docker approach / image would u use for production hosting using latest meteor?
Anyone know if a guide that works with latest meteor?
Best
Jesper
I use the base node image that is the same as the version of meteor you are developing upon.
I have a bash script that bundles up my app and then copies the bundle, some setting files and a startup script to a new docker image. I can then use this image in a docker service in my production docker swarm.
When I looked for meteor docker images to use, most were trying to download meteor to build the bundle and also act as a production image. I already had a dev environment so do not need a dockerized build environment. I only want a slim image geared for deployment. Meteor is not installed I’m my docker image at all.
Thx @pmcochrane
That sounds completely perfect and just what I need.
Would you care to share a repo or just script and docker file (as I’m not that great at docker nor bash yet).
Best
Jesper
Sorry no repo to share. Best I can do is to share a few files here for now. They may or may not be of use. I’ll struggle to find the time to explain every detail in them as there are a lot of stuff customised for use in my app. I’ll start with the docker config for building the docker image
The docker file:
FROM node:8.11.4-slim
# meteor 1.5.1 === Node 4.8.4
# meteor 1.6 === Node 8.8.1
# meteor 1.6.0.1 === Node 8.9.3
# meteor 1.6.1 === Node 8.9.4
# meteor 1.6.1.1 === Node 8.11.1
# meteor 1.6.1.3 === Node 8.11.3
# meteor 1.7 === Node 8.11.2
# meteor 1.7.0.2 === Node 8.11.3
# meteor 1.7.0.5 === Node 8.11.4
# Default values for Meteor environment variables
ENV APP_DIR=/meteor \
ROOT_URL=http://localhost \
MAIL_URL=http://localhost:25 \
MONGO_DB=meteor \
MONGO_USER=node \
MONGO_PASSWORD=123456 \
MONGO_SERVER=127.0.0.1:27017 \
MONGO_REPLICA_SET_NAME=retis_mongo \
MONGO_REPLICA_SET_URL=?replicaSet=${MONGO_REPLICA_SET_NAME} \
MONGO_URL="mongodb://${MONGO_USER}:${MONGO_PASSWORD}@${MONGO_SERVER}/${MONGO_DB}${MONGO_REPLICA_SET_URL}" \
MONGO_OPLOG_URL="mongodb://${MONGO_USER}:${MONGO_PASSWORD}@${MONGO_SERVER}/local${MONGO_REPLICA_SET_URL}&authSource=admin" \
PORT=8400 \
NODE_ENV=production
EXPOSE $PORT
HEALTHCHECK --interval=12s --timeout=12s --start-period=30s CMD node /meteor/healthcheck.js
# Install as root (otherwise node-gyp gets compiled as nobody)
USER root
WORKDIR $APP_DIR/programs/server/
# Copy bundle and scripts to the image APP_DIR
# COPY scripts/ $APP_DIR # This is now done at bundle time to reduce a layer in the docker image
COPY ./bundle/ $APP_DIR
# the install command for debian
RUN echo "Installing the node modules..." \
&& npm install -g node-gyp \
&& npm install --production --silent \
&& echo \
&& echo \
&& echo \
&& echo "Updating file permissions for the node user..." \
&& chmod -R 750 $APP_DIR \
&& chown -R node.node $APP_DIR
# Ensure the permissions are correct on the app dir
# RUN chmod -R 750 $APP_DIR \
# && chown -R node.node $APP_DIR
# start the app
WORKDIR $APP_DIR/
USER node
CMD ["/meteor/startapp.sh"]
The startapp.sh script for the docker image:
#!/bin/bash
set -e
version=$(cat programs/server/package.json | grep -i version)
replace=' "version": "'
replace2='",'
stage1=${version#$replace};
version_number=${stage1%$replace2}
export RETIS_VERSION=$version_number
echo "########################################################################"
echo "### ReTIS app version: ${RETIS_VERSION}"
echo "########################################################################"
# Set a delay to wait to start the Node process
if [[ $STARTUP_DELAY ]]; then
echo "Delaying startup for $STARTUP_DELAY seconds..."
sleep $STARTUP_DELAY
fi
export CONTAINER_IP_ADDRESS=$(hostname -i)
export CONTAINER_USER=$(grep $UID /etc/passwd | cut -d : -f1)
# Start app
cd $APP_DIR
echo "=> Starting app:"
echo "===> root_url: ${ROOT_URL}:${PORT}/"
echo "===> port ${PORT}"
echo "===> mail_url: ${MAIL_URL}"
# Expand the mongo_url secret into an environment variable
if [ -f /run/secrets/retis_mongo_url ]; then
theMongoUrl=$(cat /run/secrets/retis_mongo_url)
export MONGO_URL=${theMongoUrl}
echo "===> MONGO_URL: (from secret) ${theMongoUrl}" | sed -E 's/(.*mongouser:).*@/\1XXXXXXXXXXXXX@/'
# echo "===> MONGO_URL: (from secret) ${theMongoUrl}"
export MONGO_DB=$(echo ${MONGO_URL} | sed -E 's/(.*mongouser:).*@/\1XXXXXXXXXXXXX@/' | sed -E "s/(.*.nhs.uk:27017\/)(.*)(\?.*)/\2/")
echo "===> MONGO_DB: ${MONGO_DB}"
export MONGO_USER=$(echo ${MONGO_URL} | sed -E 's/(.*mongouser:).*@/\1XXXXXXXXXXXXX@/' | sed -E "s/(mongodb:\/\/)(.*)(:.*)/\2/") | sed -E "s/(mongouser)(.*)/\1/"
echo "===> MONGO_USER: ${MONGO_USER}"
export MONGO_SERVER=$(echo ${MONGO_URL} | sed -E 's/(.*mongouser:).*@/\1XXXXXXXXXXXXX@/' | sed -E "s/(.*\@)(.*)(\/.*)/\2/")
echo "===> MONGO_SERVER: ${MONGO_SERVER}"
export MONGO_REPLICA_SET_NAME=$(echo ${MONGO_URL} | sed -E 's/(.*mongouser:).*@/\1XXXXXXXXXXXXX@/' | sed -E "s/(.*?replicaSet=)(.*)/\2/")
echo "===> MONGO_REPLICA_SET_NAME: ${MONGO_REPLICA_SET_NAME}"
else
unset MONGO_URL
unset MONGO_DB
fi
echo "===> Database: Using \"${MONGO_DB}\" on \"${MONGO_SERVER}\" as the user \"${MONGO_USER}\""
# Expand the mongo_oplog_url secret into an environment variable
if [ -f /run/secrets/retis_mongo_oplog_url ]; then
theOplogUrl=$(cat /run/secrets/retis_mongo_oplog_url)
export MONGO_OPLOG_URL=${theOplogUrl}
echo "===> MONGO_OPLOG_URL: (from secret) ${theOplogUrl}" | sed -E 's/(.*oplogreader:).*@/\1XXXXXXXXXXXXX@/'
# echo "===> MONGO_OPLOG_URL: (from secret) ${theOplogUrl}"
else
unset MONGO_OPLOG_URL
fi
# if [ -f /run/secrets/retis_settings.json ]; then
# echo "===> Settings:"
# cat /run/secrets/retis_settings.json
# fi
ROOT_URL=${ROOT_URL}:${PORT}/ METEOR_SETTINGS=$(cat /run/secrets/retis_settings.json) node main.js
The Health check script:
var http = require("http");
var options = {
host : "localhost",
port : "8400",
timeout : 5000
};
var request = http.request(options, (res) => {
console.log(`STATUS: ${res.statusCode}`);
if (res.statusCode == 200) {
process.exit(0);
}
else {
process.exit(1);
}
});
request.on('error', function(err) {
console.log('ERROR');
process.exit(1);
});
request.end();
The following script is how to build your meteor app into a docker image:
#!/bin/bash
#
port=8400
mongodbname=retis_testing
settingsFile=./server/settings/dev.testing.json
numberOfImagesToKeep=10
source ./retis_includes.bash
# ./setup_mongo_url.bash retis_pmc; ROOT_URL=http://retismeteord1.luht.scot.nhs.uk:8400/ MAIL_URL=smtp://localhost meteor build ./.dist/ --directory --architecture os.linux.x64_64 --mobile-settings server/settings/dev.pmc.json
usage="$0 [tag: devel/testing/uat] [param-reuse-existing-bundle]"
if [ $# -lt 1 ]; then
echored "Usage: ${usage}"
echo " eg. ./createDockerImage.bash devel"
echo -e " This will rebuild the source and create a docker image tagged as ${YELLOW}devel-X.Y.Z${NC} and ${YELLOW}devel-latest${NC}"
exit 1
fi
timeStart=$(date +%H:%M:%S)
# Extract the tag for the image from the command line
tag=$1
parameterOk=0;
if [ $1 = "devel" ]; then
parameterOk=1
fi
if [ $1 = "testing" ]; then
parameterOk=1
fi
if [ $parameterOk -eq 0 ]; then
echored "Usage: ${usage}"
echo " $1 is not a valid parameter"
exit 2
fi;
shift
# Any other parameter will reuse the existing meteor bundle
createBundle=1
if [ $# -gt 0 ]; then
createBundle=0
shift
fi
. /shares/secure.files/mongodb_credentials
dockerRegistry=retismeteord1:5000
imageTag=$dockerRegistry/retis/retis
dockerFolder=.dockerimage
PACKAGE_VERSION=$(cat package.json | grep version | head -1 | awk -F: '{ print $2 }' | sed 's/[",\ ]//g')
echo -e "#################################################################"
echo -e "### Build & deploy ReTIS to a Docker image"
echo -e "### Version: ${LIGHTGRAY}${PACKAGE_VERSION}${NC} Tag: ${LIGHTGRAY}${tag}${NC}"
echo -e "#################################################################"
echogreen "==================> $timeStart Starting deployment process ==============================="
export ROOT_URL=http://localhost
export MAIL_URL=smtp://localhost:25
export MONGO_DB=$mongodbname
export MONGO_URL="mongodb://$MONGO_USER:$MONGO_PASSWORD@$MONGO_SERVER/$MONGO_DB"
echoyellow "- Using $MONGO_DB on $MONGO_SERVER as the user $MONGO_USER"
echoyellow "- Working directory: `pwd`"
########################################################################################
if [ $createBundle -gt 0 ]; then
echoyellow "- Closing any development meteor instances..."
/etc/nfsFiles/bin/devel-killMyMeteor kill
sleep 3
if [ -d ${dockerFolder}/bundle ]; then
echoyellow "- Removing an existing build bundle to make room for the new one..."
rm -rf ${dockerFolder}/bundle
fi
echoyellow "- Cleaning up build environment of dev stuff..."
rm -r .meteor/local/dev_bundle*
rm -rf .meteor/local/build
rm -rf .meteor/local/bundler-cache
rm -rf .meteor/local/dev_bundle
if [ -d node_modules ]; then
echoyellow "- Caching development node_modules folder..."
if [ -d .node_modules.dev ]; then
echoyellow "-- removing leftover dev node_modules backup folder...a previous build must have failed"
rm -rf .node_modules.dev
fi
echoyellow "-- backing up the dev node_modules folder..."
mv node_modules .node_modules.dev
if [ -f "./package-lock.json" ]; then
echoyellow "-- backing up the dev package-lock.json file..."
mv package-lock.json .node_modules.dev
fi
echoyellow "-- clearing the npm cache..."
meteor npm cache clear --force
fi
echoyellow "- Installing production node modules..."
# meteor npm install >/dev/null
meteor npm install --production >/dev/null
meteor npm install @angular/compiler-cli typescript --save-exact
timeStartMeteor=$(date +%H:%M:%S)
echogreen "==================> $timeStartMeteor Starting Meteor Build ==============================="
meteor npm list --depth=0 2>/dev/null
echoyellow "- Building the ReTIS node app for port $port..."
echo NODE_OPTIONS="--max_old_space_size=4096" ROOT_URL=${ROOT_URL}:${PORT}/ MAIL_URL=${MAIL_URL} PORT=$port AOT=1 meteor build ${dockerFolder}/ --directory --architecture os.linux.x86_64 --mobile-settings $settingsFile
# debug mode - no minify
# SKIP_LEGACY_COMPILATION=0 NODE_OPTIONS="--max_old_space_size=4096" ROOT_URL=${ROOT_URL}:${PORT}/ MAIL_URL=${MAIL_URL} PORT=$port AOT=1 meteor build --debug ${dockerFolder}/ --directory --architecture os.linux.x86_64 --mobile-settings $settingsFile
# production mode
SKIP_LEGACY_COMPILATION=0 NODE_OPTIONS="--max_old_space_size=4096" ROOT_URL=${ROOT_URL}:${PORT}/ MAIL_URL=${MAIL_URL} PORT=$port AOT=1 meteor build ${dockerFolder}/ --directory --architecture os.linux.x86_64 --mobile-settings $settingsFile
retval=$?
echoyellow "- Remove 'extra' npm files from the bundle..."
# if [ -d ${dockerFolder}/bundle/programs/server/npm/node_modules ]; then
# rm -rf ${dockerFolder}/bundle/programs/server/npm/node_modules
# fi
if [ -d ${dockerFolder}/bundle/programs/server/assets/app/node_modules ]; then
rm -rf ${dockerFolder}/bundle/programs/server/assets/app/node_modules
fi
if [ -d ${dockerFolder}/bundle/programs/web.browser/app/node_modules ]; then
rm -rf ${dockerFolder}/bundle/programs/web.browser/app/node_modules
fi
dirToClean=${dockerFolder}/bundle/programs/server/npm/node_modules
echoyellow "- Remove unneccessary files from the node_modules in the bundle: ${dirToClean}"
echo "Size before cleaning: $(du -h ${dirToClean} | tail --lines=1)"
# package.json package-lock.json yarn.lock .npmignore \
for ext in README \*.md \*.MD \*.markdown \*.mkd \*.xlsx \
.idea coverage.html coverage.json index.html CHANGES LICENSE \
\*.png \*.jpg
do
echo -n "Removing ${dirToClean}/${ext} files:"
find "${dirToClean}" -name "$ext" -exec du -sch {} + | tail --lines=1
# find "${dirToClean}" -name "$ext"
find "${dirToClean}" -name "$ext" -exec rm {} +
find "${dirToClean}" -name "$ext" -type d -exec rm -rf {} +
done
# Remove explicit folders
for folder in showdown/test \
domino/test soap/coverage hammerjs/tests \
meteor/zardak_soap/node_modules/soap/node_modules/ejs/test \
meteor/cosmos_browserify/node_modules/browserify/test \
meteor/cosmos_browserify/node_modules/browserify/example \
meteor/cosmos_browserify/node_modules/browserify/node_modules/crypto-browserify/node_modules/public-encrypt/node_modules/parse-asn1/test \
meteor/cosmos_browserify/node_modules/browserify/node_modules/stream-http/test \
xhr2/test
do
echo "Removing ${dirToClean}/${folder} folder:"
find "${dirToClean}" -wholename "${dirToClean}/${folder}" -type d -exec rm -rf {} +
done
echo "Size after cleaning: $(du -h ${dirToClean} | tail --lines=1)"
echoyellow "- Reverting to dev node_modules..."
if [ -d .node_modules.production ]; then
echoyellow "-- Removing leftover node_modules.production from a previous build..."
rm -rf .node_modules.production
fi
echoyellow "-- Moving production node_modules to node_modules.production..."
mv ./node_modules .node_modules.production
echoyellow "-- Deleting production package-lock.json..."
mv ./package-lock.json .node_modules.production/
if [ -d .node_modules.dev ]; then
echoyellow "-- Reinstalling the development node modules folder..."
mv .node_modules.dev node_modules
if [ -f "./node_modules/package-lock.json" ]; then
echoyellow "-- Reinstalling the development package-lock.json..."
mv ./node_modules/package-lock.json .
fi
echoyellow "-- clearing the npm cache..."
meteor npm cache clear --force
fi
if [ $retval -ne 0 ]; then
echored "############################################################"
echored "ERROR: Failed to build the meteor bundle. FAILING..."
echored "############################################################"
exit $retval
fi
if [ -d ${dockerFolder}/.meteor ]; then
rm -rf ${dockerFolder}/.meteor
fi
else
echoyellow "- Reusing previously bundled application...${PACKAGE_VERSION}"
fi
########################################################################################
timeStartDocker=$(date +%H:%M:%S)
echogreen "==================> $timeStartDocker Starting docker image build ==============================="
echoyellow -n "- Updating the app to the next patch version..."
meteor npm --no-git-tag-version version patch
PACKAGE_VERSION=$(cat package.json | grep version | head -1 | awk -F: '{ print $2 }' | sed 's/[",\ ]//g')
(cd ${dockerFolder}/bundle/programs/server && meteor npm --no-git-tag-version version $PACKAGE_VERSION)
echoyellow "- Copying settings to the bundle..."
if [ ! -d ${dockerFolder}/bundle/programs/server/settings ]; then
mkdir ${dockerFolder}/bundle/programs/server/settings
fi
cp $settingsFile ${dockerFolder}/bundle/programs/server/settings/settings.json
echoyellow "- Copying the docker startup scripts to the meteor bundle folder..."
cp .dockerimage/scripts/* .dockerimage/bundle/
echoyellow "- Building the docker image...${imageTag}:${PACKAGE_VERSION}"
cd ${dockerFolder}
docker build --squash -t ${imageTag}:${PACKAGE_VERSION} -t ${imageTag}:${tag}-latest -t ${imageTag}:${tag}-${PACKAGE_VERSION} .
retval=$?
if [ $retval -ne 0 ]; then
echored "###############################################################################################"
echored "ERROR: Failed to build the docker image. FAILING..."
echored "###############################################################################################"
exit $retval
fi
timePushDocker=$(date +%H:%M:%S)
if [ $numberOfImagesToKeep -gt 0 ]; then
echogreen "==============================================================================================================="
echoyellow "Removing out of date image builds - keeping ${numberOfImagesToKeep} copies"
echoyellow "---------------------------------------------------------------------------------------------------------------"
docker rmi $(docker image ls | grep "${imageTag} 2." | tail -n +${numberOfImagesToKeep} | awk '{print $3}')
fi
echogreen "==================> $timePushDocker Pushing docker images to registry ==============================="
echoyellow "- Uploading docker image to registry...${imageTag}:${PACKAGE_VERSION}"
docker push ${imageTag}
retval=$?
if [ $retval -ne 0 ]; then
echored "####################################################################################################"
echored "ERROR: Failed to push the docker image ${imageTag} to the registry. FAILING..."
echored "####################################################################################################"
exit $retval
fi
git tag -a v${PACKAGE_VERSION} -m "Tagged v${PACKAGE_VERSION}"
echoyellow "- Cleansing untagged docker images..."
docker rmi $(docker images | awk '/^<none>/ {print $3}')
echoyellow "Docker images:"
docker image ls | grep ${imageTag}
timeComplete=$(date +%H:%M:%S)
echogreen "==================> $timeComplete Complete ==============================="
echo -e "Build Summary: Version: ${LIGHTGRAY}${PACKAGE_VERSION}${NC} Tag: ${LIGHTGRAY}${tag}${NC}"
echo -e "NPM inst: ${LIGHTGRAY}${timeStart}${NC} -> ${LIGHTGRAY}${timeStartMeteor}${NC}"
echo -e "Bundling: ${LIGHTGRAY}${timeStartMeteor}${NC} -> ${LIGHTGRAY}${timeStartDocker}${NC}"
echo -e "Imaging: ${LIGHTGRAY}${timeStartDocker}${NC} -> ${LIGHTGRAY}${timePushDocker}${NC}"
echo -e "Pushing: ${LIGHTGRAY}${timePushDocker}${NC} -> ${LIGHTGRAY}${timeComplete}${NC}"
echogreen "Complete"
I can’t really describe how to setup your docker swarm but the above image will be built and pushed to a private docker registry for deployment.
The script to startup the docker swarm service is as follows:
version: "3.3"
# This service is intended to be deployed using the 'deployService.bash' script
# Define the external networks that this service requires
networks:
retis-proxy_network:
driver: overlay
external: true
services:
#-------------------------------------------------------------------------------------
# Setup retis system on 8300 - container runs internally on port 8400
#-------------------------------------------------------------------------------------
retis:
image: ${LOCAL_DOCKER_REGISTRY}:5000/retis/retis:devel-latest
networks:
- retis-proxy_network
environment:
- ROOT_URL=https://retis.some.where
- PORT=8400
- MAIL_URL=smtp://mailserver.dns.name:25
- HTTP_FORWARDED_COUNT=0
# - MONGO_URL=xxxx This is passed in via a secret
# - MONGO_OPLOG_URL=xxxx This is passed in via a secret
secrets:
- retis_mongo_url
- retis_mongo_oplog_url
- retis_settings.json
deploy:
labels:
- traefik.enable=true
- traefik.docker.network=retis-proxy_network
- traefik.backend=-
- traefik.retis.frontend.rule=Host:retis.some.where
- traefik.retis.frontend.entryPoints=http,https
- traefik.retis.frontend.redirect.permanent=true
- traefik.retis.frontend.redirect.regex=^(http|https)://(retis|retis.some.where)/(.*)
- traefik.retis.frontend.redirect.replacement=https://retis.some.where/$$3
- traefik.retis.protocol=http
- traefik.retis.port=8400
replicas: 1
placement:
constraints:
- node.role == worker
- node.labels.retisServerType == development
resources:
limits:
memory: 4G
cpus: '1'
#-------------------------------------------------------------------------------------
# Setup the secrets
#-------------------------------------------------------------------------------------
secrets:
retis_mongo_url:
external:
name: retis_devel_mongo_url
retis_mongo_oplog_url:
external:
name: retis_devel_mongo_oplog_url
retis_settings.json:
external:
name: retis_devel_settings.json
retis_postgres_readonly_password:
external:
name: retis_devel_postgres_readonly_password
retis_postgres_write_password:
external:
name: retis_devel_postgres_write_password
retis_https_htpasswd:
external:
name: retis_https_htpasswd
retis_ssl_cert.pem:
external:
# name: retis_selfsigned_ssl_cert.pem
name: retis_luht_cert.pem
retis_ssl_key.pem:
external:
# name: retis_selfsigned_ssl_key.pem
name: retis_luht_key.pem
dh4096.pem:
external:
name: dh4096.pem
THANKS for sharing @pmcochrane. I’m gonna try this out. It really a great help.
You are taking care of quite a lot “around” just building which also is really great to learn.
Thanks again,
Jesper
Take a look at Meteor Up too:
Thanks @elie
I did look at mup but two things made me stop:
best
jesper
I deployed my 1.7 app this week and it works fine. It’s still maintained well and being improved.
I’ve deployed to AWS Linux and Ubuntu from a mac and never had issues there. Pretty sure it supports the platforms you need.
Thanks Elie
A few quick questions:
Are you using docker to build your solution or are you using it to create a docker image ready to push to any docker hosting?
Would you share you script/dockerfile?
Seems to support only 1.6 and lower?
http://meteor-up.com/docs.html#meteor-support
best
Jesper
It works for meteor 1.7. I’ve tested it. It’s likely just out of date documentation.
On my phone right now, but if you look through the github repo you’ll see how they use docker.
It’s a fully deployment solution so if you’re just looking for the docker part I assume the both comments are more helpful. A few different docker images exist. Aberbix is one of them.
See Meteor Docker image for multistage Docker builds. There’s no “best” image, it depends on your needs:
That thread addresses many of these issues.
Hi @pmcochrane,
I got it to work. Thanks again. I stripped it down to make it simple and working and I’ll add back lots of your good stuff that optimizes the bundle later.
Btw in your build script when copying files you reference .dockerImage and not the variable. I also removed set -e from startapp.sh as it exited the script for unknown reasons. Finally I think there was something with port and PORT
For any other trying this out - this is my shaved down version:
/dockerstuff/Dockerfile (thats in the project folder)
FROM node:8.11.4-slim
# meteor 1.5.1 === Node 4.8.4
# meteor 1.6 === Node 8.8.1
# meteor 1.6.0.1 === Node 8.9.3
# meteor 1.6.1 === Node 8.9.4
# meteor 1.6.1.1 === Node 8.11.1
# meteor 1.6.1.3 === Node 8.11.3
# meteor 1.7 === Node 8.11.2
# meteor 1.7.0.2 === Node 8.11.3
# meteor 1.7.0.5 === Node 8.11.4
ENV APP_DIR=/meteor \
ROOT_URL=http://localhost \
MAIL_URL=http://localhost:25 \
MONGO_URL="mongodb://user:pass@hostname:31681/databasename" \
PORT=8400 \
NODE_ENV=production
EXPOSE $PORT
# Install as root (otherwise node-gyp gets compiled as nobody)
USER root
WORKDIR $APP_DIR/programs/server/
# Copy bundle and scripts to the image APP_DIR
COPY ./bundle/ $APP_DIR
# the install command for debian
RUN echo "Installing the node modules..." \
&& npm install -g node-gyp \
&& npm install --production --silent \
&& echo \
&& echo \
&& echo \
&& echo "Updating file permissions for the node user..." \
&& chmod -R 750 $APP_DIR \
&& chown -R node.node $APP_DIR
# start the app
WORKDIR $APP_DIR/
USER node
CMD ["/meteor/startapp.sh"]
and then there is the
/dockerstuff/startapp.sh (this will be copied to image and run in the container)
#!/bin/bash
cd $APP_DIR
echo "===> root_url: ${ROOT_URL}:${PORT}/"
echo "===> port ${PORT}"
echo "===> mail_url: ${MAIL_URL}"
echo "===> Database: ${MONGO_URL}"
ROOT_URL=${ROOT_URL}:${PORT} METEOR_SETTINGS=$(cat programs/server/settings/settings.json) node main.js
Finally, the script that builds it. In this version it builds the bundle every time. Its specific to the solution. change the 3 settings in the first lines
** /build.sh (this you should run manually. I do it using sh build.sh **
#!/bin/bash
#
settingsFile=./settings-development.json
dockerFolder=../dockerFolder
dockerImage=package/name
if [ -d ${dockerFolder}/bundle ]; then
echo "Removing an existing build bundle"
rm -rf ${dockerFolder}/bundle
fi
SKIP_LEGACY_COMPILATION=0 NODE_OPTIONS="--max_old_space_size=4096" meteor build ${dockerFolder}/ --directory --architecture os.linux.x86_64 --mobile-settings $settingsFile
retval=$?
if [ -d ${dockerFolder}/.meteor ]; then
echo "Removing meteor from ${dockerFolder}"
rm -rf ${dockerFolder}/.meteor
fi
echo "- Copying settings to the bundle..."
if [ ! -d ${dockerFolder}/bundle/programs/server/settings ]; then
mkdir ${dockerFolder}/bundle/programs/server/settings
fi
cp $settingsFile ${dockerFolder}/bundle/programs/server/settings/settings.json
echo "- Copying the Dockerfile ..."
cp dockerstuff/startapp.sh ${dockerFolder}/bundle/
echo "- Copying the docker startup scripts to the meteor bundle folder..."
cp dockerstuff/Dockerfile ${dockerFolder}
cd ${dockerFolder}
docker build --squash -t $dockerImage .
retval=$?
if [ $retval -ne 0 ]; then
echo "FAILING..."
exit $retval
fi
Thanks @GeoffreyBooth
I’m gonna save this one for later when ci/cd becomes relevant (right now we’re only 1 (me) working on some early stuff).
Best
Jesper
Glad it helped you out. I spent a good while figuring most of this out myself. I have to host all servers internally on a secure network and I found most of the docker materials to be assuming you were on the internet using one of the cloud providers. It was a case of trial and error for me to get this far.
Btw in your build script when copying files you reference .dockerImage and not the variable. I also removed set -e from startapp.sh as it exited the script for unknown reasons. Finally I think there was something with port and PORT
Thanks for the bugfix with .dockerimage :). Never noticed it as I’ve always used the same folder name.
Set -e I think was something to do with making sure the service automatically restarted if it fails. Can’t quite remember why or when I put it there.
The $port and $PORT variables are not really the same thing in both scripts. $port 8400 in the build script is what port I want the node app to appear on in the docker image.
The service config file passes PORT to the docker service to start the container on. This doesn’t have to be 8400. For instance you can have a development docker service on 8300, a testing service on 8350, a uat service on 8500 and the live service on 8400 by defining 4 docker service compose file with different settings and opening up ports to be accessable.
However, for my docker swarm, I now run a reverse proxy called traefik that handles SSL termination and routing into the docker swarm for the different app versions via dns address resolution in the url. I think the compose file above has some of this config left in.