├── .eslintignore ├── .eslintrc.yml ├── .github └── workflows │ └── test-publish.yaml ├── .gitignore ├── .releaserc.json ├── LICENSE ├── image ├── Dockerfile ├── scripts │ ├── entry.sh │ ├── node-version.js │ ├── onbuild-node.sh │ ├── setup_nvm.sh │ └── start.sh └── setup │ ├── .eslintrc.yml │ ├── default_node.js │ ├── install_node.js │ ├── install_nvm.sh │ ├── setup_root_nvm.sh │ └── versions.json ├── package-lock.json ├── package.json ├── readme.md ├── root-image └── Dockerfile └── tests ├── prepare-release.sh ├── publish.sh └── test.sh /.eslintignore: -------------------------------------------------------------------------------- 1 | app 2 | bundle 3 | node_modules 4 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | node: true 3 | extends: 'eslint:recommended' 4 | rules: 5 | indent: 6 | - error 7 | - 2 8 | linebreak-style: 9 | - error 10 | - unix 11 | quotes: 12 | - error 13 | - single 14 | semi: 15 | - error 16 | - always 17 | -------------------------------------------------------------------------------- /.github/workflows/test-publish.yaml: -------------------------------------------------------------------------------- 1 | name: Test-Publish 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | 8 | jobs: 9 | test: 10 | runs-on: ubuntu-latest 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | meteorOptions: 15 | - '--release=1.2.1' 16 | - '--release=1.3.5.1' 17 | - '--release=1.4.4.6' 18 | - '--release=1.5.4.1' 19 | - '--release=1.6.1.4' 20 | - '--release=1.7.0.5' 21 | - '--release=1.8.1' 22 | - '--release=1.9.3' 23 | - '--release=1.10.2' 24 | - '--release=1.11.1' 25 | - '--release=2.1.1' 26 | - '--release=2.2' 27 | - '--release=2.3.2' 28 | - '--release=2.4.1' 29 | - '--release=2.5.6' 30 | - '--release=2.6' 31 | - '--release=2.7' 32 | - '--release=2.8.0' 33 | - '--release=2.9.0' 34 | - '--release=2.10.0' 35 | - '--release=2.11.0' 36 | - '--release=2.12' 37 | - '--release=2.13.3' 38 | # Latest version 39 | - 40 | steps: 41 | - name: Checkout code 42 | uses: actions/checkout@v2 43 | 44 | - uses: actions/cache@v2 45 | name: Meteor cache 46 | with: 47 | path: ~/.meteor 48 | key: ${{ runner.os }}-meteor-${{ matrix.meteorOptions }} 49 | 50 | - uses: satackey/action-docker-layer-caching@v0.0.11 51 | continue-on-error: true 52 | 53 | - name: Install Node.js 54 | uses: actions/setup-node@v2 55 | with: 56 | node-version: '14.x' 57 | 58 | - name: Install npm dependencies 59 | run: npm ci 60 | 61 | - name: Run tests 62 | run: | 63 | export PATH=$HOME/.meteor:$PATH 64 | export METEOR_TEST_OPTION=${{ matrix.meteorOptions }} 65 | npm test 66 | 67 | 68 | publish: 69 | if: ${{ github.ref == 'refs/heads/master' }} 70 | runs-on: ubuntu-latest 71 | needs: test 72 | steps: 73 | - name: Checkout code 74 | uses: actions/checkout@v2 75 | 76 | - name: Use Node.js ${{ matrix.node-version }} 77 | uses: actions/setup-node@v2 78 | with: 79 | node-version: '14.x' 80 | 81 | - name: Login to Docker Hub 82 | uses: docker/login-action@v1 83 | with: 84 | username: ${{ secrets.DOCKER_USERNAME }} 85 | password: ${{ secrets.DOCKER_PASSWORD }} 86 | 87 | - name: Install npm dependencies 88 | run: npm ci 89 | 90 | - name: Release 91 | run: npm run release 92 | env: 93 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 94 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | # from tests 4 | bundle 5 | app 6 | -------------------------------------------------------------------------------- /.releaserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "branches": "master", 3 | "repositoryUrl": "https://github.com/zodern/meteor-docker.git", 4 | "verifyConditions": [ 5 | "@semantic-release/github" 6 | ], 7 | "publish": [ 8 | { 9 | "path": "@semantic-release/exec", 10 | "cmd": "bash ./tests/publish.sh ${nextRelease.version}" 11 | }, 12 | "@semantic-release/github" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 zodern 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /image/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:bullseye-slim 2 | LABEL maintainer="zodern" 3 | RUN apt-get update && \ 4 | apt-get install -y curl python python3 make g++ bzip2 ca-certificates --no-install-recommends && \ 5 | rm -rf /var/lib/apt/lists/* 6 | 7 | RUN useradd --create-home --shell /bin/bash --uid 1000 --user-group app 8 | 9 | RUN mkdir /built_app && \ 10 | mkdir /bundle && \ 11 | chown -R app:app /home/app/ && \ 12 | chown -R app:app /built_app && \ 13 | chown -R app:app /bundle && \ 14 | chmod 700 /built_app && \ 15 | chmod 700 /bundle 16 | 17 | USER app 18 | WORKDIR /home/app 19 | RUN mkdir scripts && mkdir setup 20 | COPY ./setup ./setup 21 | RUN bash setup/install_nvm.sh 22 | 23 | COPY ./scripts ./scripts 24 | 25 | ONBUILD USER app 26 | ONBUILD ARG NODE_VERSION='8.17.0' 27 | ONBUILD ARG EXACT_NODE_VERSION=false 28 | ONBUILD RUN bash ./scripts/onbuild-node.sh 29 | ONBUILD ENV NODE_PATH=/home/app/.onbuild-node/lib/node_modules PATH=/home/app/.onbuild-node/bin:$PATH 30 | 31 | USER root 32 | RUN bash setup/setup_root_nvm.sh 33 | EXPOSE 3000 34 | ENTRYPOINT bash /home/app/scripts/entry.sh 35 | -------------------------------------------------------------------------------- /image/scripts/entry.sh: -------------------------------------------------------------------------------- 1 | change_user () { 2 | HOST_UID=$(stat -c %u $PWD) 3 | HOST_GUID=$(stat -c %g $PWD) 4 | 5 | if [ ! "${HOST_UID}" = "$(id -u app)" ]; then 6 | echo "HOST_UID: $HOST_UID" 7 | echo "HOST_GUID: $HOST_GUID" 8 | 9 | usermod -u $HOST_UID app 10 | groupmod -g $HOST_GUID app 11 | 12 | chown -R app:app /home/app 13 | fi 14 | } 15 | 16 | if [ -e /bundle/bundle.tar.gz ]; then 17 | cd /bundle 18 | change_user 19 | chown -R app:app *.tar.gz || true 20 | 21 | else 22 | cd /built_app 23 | change_user 24 | fi 25 | 26 | if [[ $EUID -ne 0 ]]; then 27 | bash /home/app/scripts/start.sh 28 | else 29 | su -c "bash /home/app/scripts/start.sh" app 30 | fi 31 | -------------------------------------------------------------------------------- /image/scripts/node-version.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | 4 | var logsEnabled = process.env.DEBUG_NODE_VERSION === '0'; 5 | 6 | var logStream; 7 | if (logsEnabled) { 8 | logStream = fs.createWriteStream('/home/app/scripts/log.txt', {'flags': 'w'}); 9 | } 10 | 11 | function log(text) { 12 | if(logsEnabled) { 13 | logStream.write(text + '\n'); 14 | } 15 | } 16 | 17 | function logFinish(text) { 18 | if (logsEnabled) { 19 | logStream.end(text + '\n'); 20 | } 21 | } 22 | 23 | log('starting'); 24 | 25 | var starPath = path.resolve(process.cwd(), 'star.json'); 26 | var starJson; 27 | var starVersion = null; 28 | var textPath = path.resolve(process.cwd(), '.node_version.txt'); 29 | var textVersion = null; 30 | 31 | var installedVersions = require('../setup/versions.json'); 32 | var wantExact = JSON.parse(process.env.EXACT_NODE_VERSION || 'false'); 33 | var nodeVersion; 34 | 35 | log('wantExact: ' + wantExact); 36 | 37 | try { 38 | starJson = require(starPath); 39 | if (starJson.nodeVersion) { 40 | starVersion = starJson.nodeVersion; 41 | } 42 | } catch (e) { 43 | // empty 44 | } 45 | 46 | try { 47 | textVersion = fs.readFileSync(textPath).toString('utf8').slice(1).trim(); 48 | } catch (e) { 49 | // empty 50 | } 51 | 52 | log('starVersion: ', starVersion); 53 | log('textVersion: ' + textVersion); 54 | 55 | nodeVersion = process.env.NODE_VERSION || starVersion || textVersion; 56 | 57 | if (!wantExact) { 58 | if (!nodeVersion) { 59 | console.error('Unable to find config for node version.'); 60 | console.error('Are you sure the app bundle is available in the container?'); 61 | console.error('Learn how to use the image at https://github.com/zodern/meteor-docker'); 62 | process.exit(1); 63 | } 64 | 65 | // Find a version already installed with the same major version 66 | var majorVersion = nodeVersion.split('.')[0]; 67 | if (majorVersion[0] === 'v') { 68 | majorVersion = majorVersion.slice(1); 69 | } 70 | log('majorVersion: ' + majorVersion); 71 | 72 | for (var i = 0; i < installedVersions.length; i++) { 73 | if (installedVersions[i].node.split('.')[0] === majorVersion) { 74 | nodeVersion = installedVersions[i].node; 75 | log('found major version: ' + nodeVersion); 76 | break; 77 | } 78 | } 79 | } 80 | 81 | // eslint-disable-next-line 82 | console.log(nodeVersion); 83 | logFinish('finished: ' + nodeVersion); 84 | -------------------------------------------------------------------------------- /image/scripts/onbuild-node.sh: -------------------------------------------------------------------------------- 1 | . ./scripts/setup_nvm.sh 2 | 3 | rm -rf /home/app/.onbuild-node 4 | mkdir -p /home/app/.onbuild-node 5 | 6 | ln -s $NODE_PATH /home/app/.onbuild-node 7 | -------------------------------------------------------------------------------- /image/scripts/setup_nvm.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | export NVM_DIR="/home/app/.nvm" 3 | [ -s "$NVM_DIR/nvm.sh" ] 4 | . "$NVM_DIR/nvm.sh" 5 | 6 | NODE_VERSION="$(node /home/app/scripts/node-version.js)" 7 | # Replace a possible 'v' prefix 8 | NODE_VERSION="$(echo $NODE_VERSION | sed 's/^v//')" 9 | echo "NODE_VERSION=$NODE_VERSION" 10 | 11 | if [[ $DEBUG_NODE_VERSION == "0" ]]; then 12 | cat /home/app/scripts/log.txt || true 13 | fi 14 | 15 | MAJOR_NODE_VERSION=`echo $NODE_VERSION | awk -F. '{print $1}'` 16 | MINOR_NODE_VERSION=`echo $NODE_VERSION | awk -F. '{print $2}'` 17 | PATCH_NODE_VERSION=`echo $NODE_VERSION | awk -F. '{print $3}'` 18 | 19 | echo "Node: $NODE_VERSION (parsed: $MAJOR_NODE_VERSION.$MINOR_NODE_VERSION.$PATCH_NODE_VERSION)" 20 | 21 | if [[ $MAJOR_NODE_VERSION == "14" && $MINOR_NODE_VERSION -ge 21 && $PATCH_NODE_VERSION -ge 4 ]]; then 22 | NODE_INSTALL_PATH="/home/app/.nvm/versions/node/v$NODE_VERSION" 23 | 24 | if [ -d $NODE_INSTALL_PATH ]; then 25 | echo "Meteor's custom v14 LTS Node version is already installed ($NODE_VERSION)" 26 | else 27 | echo "Using Meteor's custom NodeJS v14 LTS version" 28 | 29 | # https://hub.docker.com/layers/meteor/node/14.21.4/images/sha256-f4e19b4169ff617118f78866c2ffe392a7ef44d4e30f2f9fc31eef2c35ceebf3?context=explore 30 | curl "https://static.meteor.com/dev-bundle-node-os/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.gz" | tar xzf - -C /tmp/ 31 | mv /tmp/node-v$NODE_VERSION-linux-x64 $NODE_INSTALL_PATH 32 | fi 33 | 34 | nvm use $NODE_VERSION 35 | else 36 | echo "Using NVM" 37 | nvm install $NODE_VERSION 38 | fi 39 | 40 | nvm alias default $NODE_VERSION 41 | export NODE_PATH=$(dirname $(nvm which $(node --version))) 42 | -------------------------------------------------------------------------------- /image/scripts/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | if [ -e /bundle/bundle.tar.gz ]; then 5 | echo "Found /bundle/bundle.tar.gz" 6 | cd /bundle 7 | 8 | chmod -v 777 bundle.tar.gz || true 9 | 10 | echo "=> Extracting bundle" 11 | 12 | TAR_OPTIONS=$([ $EUID == 0 ] && echo "" || echo "--no-same-owner") 13 | NPM_OPTIONS=$([ $EUID == 0 ] && echo " --unsafe-perm" || echo "") 14 | 15 | tar $TAR_OPTIONS -xzf bundle.tar.gz 16 | 17 | cd /bundle/bundle 18 | 19 | echo "=> Setting node version" 20 | . /home/app/scripts/setup_nvm.sh 21 | 22 | echo "=> Installing npm dependencies" 23 | cd ./programs/server && npm install $NPM_OPTIONS $NPM_INSTALL_OPTIONS 24 | 25 | cd ../.. 26 | else 27 | cd /built_app 28 | echo "=> Setting node version" 29 | . /home/app/scripts/setup_nvm.sh 30 | fi 31 | 32 | export PORT=${PORT:-3000} 33 | echo "=> Starting meteor app on port $PORT" 34 | node main.js 35 | -------------------------------------------------------------------------------- /image/setup/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | es6: true 3 | node: true 4 | extends: 'eslint:recommended' 5 | rules: 6 | indent: 7 | - error 8 | - 2 9 | linebreak-style: 10 | - error 11 | - unix 12 | quotes: 13 | - error 14 | - single 15 | semi: 16 | - error 17 | - always 18 | no-console: 19 | - 0 -------------------------------------------------------------------------------- /image/setup/default_node.js: -------------------------------------------------------------------------------- 1 | var versions = require('./versions.json'); 2 | 3 | // eslint-disable-next-line 4 | console.log(versions[versions.length - 1].node); 5 | -------------------------------------------------------------------------------- /image/setup/install_node.js: -------------------------------------------------------------------------------- 1 | var versions = require('./versions.json'); 2 | var { spawnSync, execSync } = require('child_process'); 3 | var fs = require('fs'); 4 | 5 | var nvm = '. "$NVM_DIR/nvm.sh" && nvm'; 6 | 7 | process.env.PATH += ':/home/app/.nvm'; 8 | console.log('\nInstalling node versions'); 9 | 10 | function log({ output }) { 11 | if (output) { 12 | console.log(output.toString()); 13 | } 14 | } 15 | 16 | for (var i = 0; i < versions.length; i++) { 17 | var version = versions[i]; 18 | 19 | var output = spawnSync(nvm, ['install', version.node], { 20 | stdio: 'pipe', 21 | shell: true 22 | }); 23 | log(output); 24 | 25 | if (version.npm === (versions[i - 1] ? versions[i - 1].npm : '')) { 26 | console.log('symlinking duplicate npm version'); 27 | const prevVersion = versions[i - 1]; 28 | 29 | // This is only correct when version.node is 0.10.x since nvm stores 30 | // newer versions in a different location 31 | const targetPath = `/home/app/.nvm/v${version.node}/lib/node_modules/npm`; 32 | execSync(`rm -rf ${targetPath}`); 33 | fs.symlinkSync( 34 | `/home/app/.nvm/versions/node/v${prevVersion.node}/lib/node_modules/npm`, 35 | targetPath 36 | ); 37 | } else if (version.npm !== 'default') { 38 | var npmOutput = execSync(`${nvm} use ${version.node} && npm install --global npm@${version.npm}`, { 39 | stdio: 'pipe' 40 | }); 41 | log(npmOutput); 42 | } 43 | 44 | console.log(`\n Finished installing ${version.node}\n`); 45 | } 46 | -------------------------------------------------------------------------------- /image/setup/install_nvm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.2/install.sh | bash 5 | 6 | export NVM_DIR="/home/app/.nvm" 7 | [ -s "$NVM_DIR/nvm.sh" ] 8 | . "$NVM_DIR/nvm.sh" 9 | 10 | # initial version used during setup 11 | nvm install 16 12 | 13 | node /home/app/setup/install_node.js 14 | 15 | DEFAULT_VERSION="$(node /home/app/setup/default_node.js)" 16 | nvm alias default $DEFAULT_VERSION 17 | nvm use $DEFAULT_VERSION 18 | 19 | nvm uninstall 16 20 | nvm cache clear 21 | -------------------------------------------------------------------------------- /image/setup/setup_root_nvm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cat <<'EOF' >> /root/.bashrc 4 | export NVM_DIR="/home/app/.nvm" 5 | [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm 6 | EOF 7 | -------------------------------------------------------------------------------- /image/setup/versions.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "node": "8.17.0", 4 | "npm": "default" 5 | } 6 | ] 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "meteor-docker", 3 | "version": "0.1.0", 4 | "description": "", 5 | "scripts": { 6 | "pretest": "npm run lint", 7 | "test": "cd tests && bash test.sh", 8 | "lint": "eslint **/*.js", 9 | "release": "bash ./tests/prepare-release.sh" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/zodern/meteor-docker.git" 14 | }, 15 | "author": "zodern", 16 | "license": "MIT", 17 | "bugs": { 18 | "url": "https://github.com/zodern/meteor-docker/issues" 19 | }, 20 | "homepage": "https://github.com/zodern/meteor-docker#readme", 21 | "private": true, 22 | "dependencies": { 23 | "@semantic-release/exec": "^6.0.3", 24 | "eslint": "^8.13.0", 25 | "semantic-release": "^19.0.2" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | zodern/meteor Docker Image 2 | === 3 | 4 | ![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/zodern/meteor-docker/test-publish.yaml?branch-master&style=flat-square) ![Docker Pulls](https://img.shields.io/docker/pulls/zodern/meteor?style=flat-square) 5 | 6 | Docker image to run Meteor apps. 7 | 8 | ### Features 9 | 10 | - One image supports every Meteor version (tested with 1.2 - 2.13 and newer) 11 | - Mostly supports Meteor 3 (requires making `programs/server/shrinkwrap.json` [writable](https://github.com/meteor/meteor/issues/12932) due to a bug in Meteor 3) 12 | - Automatically uses correct node and npm version 13 | - Runs app as non-root user 14 | - Compatible with Meteor up 15 | 16 | ## Tags 17 | 18 | - `zodern/meteor` The recommended version. Runs the app as a non-root user. 19 | - `zodern/meteor:root` Compatible with meteord. Runs the app as the root user and has phantomjs installed. Any notes below about permissions do not apply to this image. 20 | - `zodern/meteor:slim` Coming soon. Is a smaller image without node or npm pre-installed. During ONBUILD or when starting the app, it will install the correct version. 21 | 22 | ## How To Use 23 | 24 | ### Permissions 25 | 26 | This image runs the app with the `app` user. The owner of any files or folders your app uses inside the Docker container should be changed to `app`. 27 | 28 | ### Meteor Up 29 | 30 | In your mup config, change `app.docker.image` to `zodern/meteor`. 31 | 32 | If you want to use mup's `buildInstructions` option to run commands as root, you can do so by temporarily changing the user: 33 | 34 | ``` 35 | buildInstructions: [ 36 | 'USER root', 37 | 'RUN apt-get update && apt-get install -y imagemagick graphicsmagick', 38 | 'USER app' 39 | ] 40 | ``` 41 | 42 | ### Compressed bundle 43 | 44 | You can create the bundle with the `meteor build` command. The bundle should be available in the container at `/bundle/bundle.tar.gz`. 45 | 46 | #### Dockerfile 47 | 48 | Create a file named `Dockerfile` and add the following: 49 | 50 | ```Dockerfile 51 | FROM zodern/meteor 52 | COPY --chown=app:app ../path/to/bundle.tar.gz /bundle/bundle.tar.gz 53 | ``` 54 | 55 | Then build and run the image with 56 | 57 | ```bash 58 | docker build --build-arg EXACT_NODE_VERSION= --build-arg NODE_VERSION= -t meteor-app . 59 | docker run --env NODE_VERSION= --env EXACT_NODE_VERSION= --name my-meteor-app meteor-app 60 | ``` 61 | 62 | And your app will be running on port 3000 63 | 64 | The `(--build-arg || --env) NODE_VERSION=` is optional, and only needed if a command in your docker file will use a specific node or npm version. 65 | 66 | If any Node versions in `image/setup/versions.json` match the same major version of Node your app needs, the version in `versions.json` is used instead since it will have additional security fixes missing in the version Meteor specifies. If there is no matching major version, it will use the exact version Meteor specifies. To always use the exact version, you can use set `(--build-arg || --env) EXACT_NODE_VERSION=true` 67 | 68 | #### Volume 69 | 70 | Run 71 | 72 | ```bash 73 | docker run --name my-meteor-app -v /path/to/folder/with/bundle:/bundle -p 3000:3000 -e "ROOT_URL=http://app.com" zodern/meteor 74 | ``` 75 | 76 | ### Built app 77 | 78 | `Built app` refers to an uncompressed bundle that already has had it's npm dependencies installed. 79 | 80 | When using a compressed bundle, the bundle is decompressed and the app's npm dependencies are installed every time the app is started, which can take a while. By using this method instead, both steps are done before the container is started, allowing it to start much faster. Meteor up's `Prepare Bundle` feature uses this. 81 | 82 | Before following the instructions in either of the next two sections, build your app with `meteor build --directory ../path/to/put/bundle`. 83 | 84 | The bundle should be available in the container at `/built_app`. 85 | 86 | #### Docker Image 87 | 88 | Create a file named `Dockerfile` and copy the following into it: 89 | 90 | ```Dockerfile 91 | FROM zodern/meteor 92 | COPY --chown=app:app ./path/to/bundle /built_app 93 | RUN cd /built_app/programs/server && npm install 94 | ``` 95 | 96 | Then build and run your image with: 97 | 98 | ```bash 99 | docker build -t meteor-app --build-arg NODE_VERSION="node version" . 100 | docker run --name my-meteor-app -p 3000:3000 -e "ROOT_URL=http://app.com" meteor-app 101 | ``` 102 | 103 | #### Volume 104 | 105 | If possible, you should create a docker image as described in the previous instructions since it is more reliable. 106 | 107 | First, make sure you have the correct version of node installed for the Meteor version your app uses, and then run 108 | 109 | ```bash 110 | cd /path/to/bundle 111 | cd programs/server 112 | npm install 113 | ``` 114 | 115 | Next, start the docker container with 116 | 117 | ```bash 118 | docker run --name my-meteor-app -v /path/to/bundle:/built_app -p 3000:3000 -e "ROOT_URL=http://app.com" zodern/meteor 119 | ``` 120 | 121 | ### Options 122 | 123 | #### NPM_INSTALL_OPTIONS 124 | 125 | When using a compressed bundle, you can specify the arguments used when running `npm install` by setting the environment variable `NPM_INSTALL_OPTIONS`. 126 | 127 | ## Contributing 128 | 129 | Tests can be run with `npm test`. The tests can not be run on Windows, though WSL does work. Docker should be installed before running the tests. 130 | 131 | Commit messages should start with one of these to allow the changelog and version to be updated correctly: 132 | 133 | - `fix:` fixes a bug 134 | - `feat:` adds a feature 135 | - `perf:` 136 | - `docs:` 137 | - `chore:` 138 | - `ci:` 139 | 140 | If the change is a breaking change, add `BREAKING CHANGE:` to the commit description 141 | -------------------------------------------------------------------------------- /root-image/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM zodern/meteor 2 | LABEL maintainer="zodern" 3 | USER root 4 | RUN apt-get update && \ 5 | apt-get install -y libfontconfig1 libfreetype6 && \ 6 | rm -rf /var/lib/apt/lists/* 7 | RUN export VERSION="2.1.1" && \ 8 | curl -L -o ./phantomjs.tar.bz2 https://github.com/Medium/phantomjs/releases/download/v$VERSION/phantomjs-$VERSION-linux-x86_64.tar.bz2 && \ 9 | mkdir phantomjs && \ 10 | tar xvjf phantomjs.tar.bz2 -C ./phantomjs --strip-components=1 && \ 11 | mv phantomjs /usr/local/share && \ 12 | ln -sf /usr/local/share/phantomjs/bin/phantomjs /usr/local/bin && \ 13 | rm -rf ./phantomjs ./phantomjs.tar.bz2 14 | ONBUILD USER root 15 | ONBUILD ARG NODE_VERSION='8.17.0' 16 | ONBUILD ARG EXACT_NODE_VERSION=false 17 | ONBUILD RUN bash ./scripts/onbuild-node.sh 18 | ONBUILD ENV NODE_PATH=/home/app/.onbuild-node/lib/node_modules 19 | ONBUILD ENV PATH=/home/app/.onbuild-node/bin:$PATH 20 | ENTRYPOINT bash /home/app/scripts/start.sh 21 | -------------------------------------------------------------------------------- /tests/prepare-release.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | 3 | docker build -t zodern/meteor ./image 4 | docker build -t zodern/meteor:root ./root-image 5 | 6 | semantic-release --debug 7 | -------------------------------------------------------------------------------- /tests/publish.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | VERSION="$1" 3 | 4 | # Normal image 5 | docker tag zodern/meteor zodern/meteor:latest 6 | docker tag zodern/meteor zodern/meteor:$VERSION 7 | 8 | docker push zodern/meteor:latest 9 | docker push zodern/meteor:$VERSION 10 | 11 | # root image 12 | docker tag zodern/meteor:root zodern/meteor:$VERSION-root 13 | 14 | docker push zodern/meteor:root 15 | docker push zodern/meteor:$VERSION-root 16 | -------------------------------------------------------------------------------- /tests/test.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | docker build -t zodern/meteor ../image 3 | docker build -t zodern/meteor:root ../root-image 4 | 5 | command -v meteor >/dev/null 2>&1 || { curl https://install.meteor.com/ | sh; } 6 | 7 | docker rm -f meteor-docker-test >/dev/null || true 8 | 9 | sudo rm -rf /tmp/docker-meteor-tests 10 | mkdir /tmp/docker-meteor-tests 11 | cp -r ../ /tmp/docker-meteor-tests 12 | cd /tmp/docker-meteor-tests/tests 13 | 14 | rm -rf ./app 15 | rm -rf ./bundle 16 | rm -rf ./archive 17 | mkdir ./app 18 | mkdir ./bundle 19 | mkdir ./archive 20 | 21 | # Shows output whe the command fails 22 | hide_output () { 23 | file='./command_logs.txt' 24 | rm -f "$file" || true 25 | set +e 26 | "$@" > "$file" 2>&1 27 | code=$? 28 | set -e 29 | [ "$code" -eq 0 ] || cat "$file" 30 | 31 | return "$code" 32 | } 33 | 34 | change_version() { 35 | echo "=> Creating app with ${1:-"latest Meteor version"}" 36 | 37 | cd .. 38 | rm -rf app 39 | hide_output meteor create $1 app 40 | cd app 41 | 42 | # Remove hot-module-replacement package since it causes errors with debug builds 43 | sed -i -e '/hot-module-replacement/d' .meteor/packages 44 | 45 | sleep 1 46 | 47 | echo "=> npm install babel-runtime" 48 | hide_output meteor npm install babel-runtime -q || true 49 | 50 | # At some point, the default app started creating Mongo collections 51 | # Remove the default server code so we can test without Mongo 52 | if [ -f ./server/main.js ]; then 53 | echo "" > ./server/main.js 54 | fi 55 | } 56 | 57 | build_app() { 58 | echo "=> Building app" 59 | sudo rm -rf /tmp/docker-meteor-tests/bundle || true 60 | meteor build ../bundle --debug 61 | } 62 | 63 | build_app_directory() { 64 | echo "=> Building app" 65 | meteor build --directory --debug ../bundle 66 | } 67 | 68 | test_bundle() { 69 | echo "=> Testing bundle volume" 70 | mv ../bundle/app.tar.gz ../bundle/bundle.tar.gz 71 | 72 | echo "==> Creating docker container" 73 | 74 | docker run \ 75 | -v "$PWD"/../bundle:/bundle \ 76 | -e "ROOT_URL=http://localhost.com" \ 77 | -e "NPM_INSTALL_OPTIONS=--no-bin-links" \ 78 | -p 3000:3000 \ 79 | -d \ 80 | --name meteor-docker-test \ 81 | "$DOCKER_IMAGE" 82 | } 83 | 84 | test_bundle_docker() { 85 | echo "=> Testing bundle image" 86 | NODE_VERSION=$(meteor node --version) 87 | 88 | echo "==> Creating image" 89 | mv ../bundle/app.tar.gz ../bundle/bundle.tar.gz 90 | cd ../bundle 91 | 92 | cat > Dockerfile << EOT 93 | FROM $DOCKER_IMAGE 94 | COPY ./bundle.tar.gz /bundle/bundle.tar.gz 95 | EOT 96 | 97 | hide_output docker build --build-arg NODE_VERSION="$NODE_VERSION" -t zodern/meteor-test . 98 | docker run --name meteor-docker-test \ 99 | -e "ROOT_URL=http://app.com" \ 100 | -p 3000:3000 \ 101 | -d \ 102 | zodern/meteor-test 103 | 104 | cd ../app 105 | } 106 | 107 | test_built_docker() { 108 | echo "=> Testing built_app image" 109 | NODE_VERSION=$(meteor node --version) 110 | 111 | echo "==> Creating image" 112 | 113 | cd ../bundle/bundle 114 | cat < Dockerfile 115 | FROM $DOCKER_IMAGE 116 | COPY --chown=app:app . /built_app 117 | RUN cd /built_app/programs/server && npm install $NPM_OPTIONS 118 | EOT 119 | 120 | hide_output docker build --build-arg NODE_VERSION="$NODE_VERSION" -t zodern/meteor-test . 121 | docker run --name meteor-docker-test \ 122 | -e "ROOT_URL=http://app.com" \ 123 | -p 3000:3000 \ 124 | -d \ 125 | zodern/meteor-test 126 | 127 | cd ../../app 128 | } 129 | 130 | verify() { 131 | TIMEOUT=300 132 | elaspsed=0 133 | success=0 134 | 135 | while [[ "$elaspsed" != "$TIMEOUT" && $success == 0 ]]; do 136 | sleep 1 137 | elaspsed=$((elaspsed+1)) 138 | 139 | curl -s \ 140 | localhost:3000 >/dev/null \ 141 | && success=1 142 | 143 | done 144 | 145 | if [ "$success" == "0" ]; then 146 | echo "FAIL" 147 | docker logs meteor-docker-test --tail 150 148 | exit 1 149 | fi 150 | 151 | echo "SUCCESS" 152 | docker rm -f meteor-docker-test >/dev/null || true 153 | } 154 | 155 | test_version() { 156 | change_version "$1" 157 | 158 | build_app 159 | test_bundle 160 | verify 161 | 162 | build_app 163 | test_bundle_docker 164 | verify 165 | 166 | build_app_directory 167 | test_built_docker "$1" 168 | verify 169 | } 170 | 171 | test_versions() { 172 | echo "--- Testing Docker Image $DOCKER_IMAGE ---" 173 | 174 | if [[ -z ${METEOR_TEST_OPTION+x} ]]; then 175 | test_version "--release=1.2.1" 176 | test_version "--release=1.3.5.1" 177 | test_version "--release=1.4.4.6" 178 | test_version "--release=1.5.4.1" 179 | test_version "--release=1.6.1.4" 180 | test_version "--release=1.7.0.5" 181 | test_version "--release=1.8.1" 182 | test_version "--release=1.9.3" 183 | test_version "--release=1.10.2" 184 | test_version "--release=1.11.1" 185 | test_version "--release=2.1.1" 186 | test_version "--release=2.2" 187 | test_version "--release=2.3.2" 188 | test_version "--release=2.4.1" 189 | test_version "--release=2.5.6" 190 | test_version "--release=2.6" 191 | test_version "--release=2.7" 192 | test_version "--release=2.8.0" 193 | test_version "--release=2.9.0" 194 | test_version "--release=2.10.0" 195 | test_version "--release=2.11.0" 196 | test_version "--release=2.12" 197 | test_version "--release=2.13.3" 198 | 199 | # Latest version 200 | test_version 201 | else 202 | test_version "$METEOR_TEST_OPTION" 203 | fi 204 | } 205 | 206 | # Needed for old Meteor versions 207 | export NODE_TLS_REJECT_UNAUTHORIZED=0 208 | # Fixes some Meteor versions crashing when creating a debug build 209 | export DISABLE_REACT_FAST_REFRESH="true" 210 | 211 | DOCKER_IMAGE="zodern/meteor" 212 | NPM_OPTIONS="" 213 | test_versions 214 | 215 | DOCKER_IMAGE="zodern/meteor:root" 216 | NPM_OPTIONS="--unsafe-perm" 217 | test_versions 218 | --------------------------------------------------------------------------------