├── screenshots ├── pm2.png └── status.png ├── deploy-build.sh ├── pm2.json ├── README.md └── nginx.conf /screenshots/pm2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramezrafla/meteor-deployment/HEAD/screenshots/pm2.png -------------------------------------------------------------------------------- /screenshots/status.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ramezrafla/meteor-deployment/HEAD/screenshots/status.png -------------------------------------------------------------------------------- /deploy-build.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | cd ../build 3 | rm -rf bundle 4 | tar xvf meteor.tar.gz 5 | cd bundle 6 | (cd programs/server && npm install) 7 | pm2 restart app-1 8 | sleep 5 9 | pm2 restart app-2 10 | sleep 5 11 | pm2 restart app-3 12 | -------------------------------------------------------------------------------- /pm2.json: -------------------------------------------------------------------------------- 1 | { 2 | "apps": 3 | [ 4 | { 5 | "name": "pm2-web", 6 | "cwd":"/usr/lib/node_modules/pm2-web", 7 | "script": "pm2-web.js", 8 | "args": "['--production', '--www.host=localhost', '--www.port=9001']" 9 | }, 10 | { 11 | "name": "mongodb", 12 | "cwd":"/home/meteor/", 13 | "script": "/usr/bin/mongod", 14 | "args": "--port 27017 --dbpath db/ --replSet meteor --logpath db/db.log", 15 | "interpeter":"none", 16 | "exec-mode":"fork" 17 | }, 18 | { 19 | "name": "redis", 20 | "cwd":"/home/meteor", 21 | "script": "redis-server", 22 | "args":"", 23 | "interpeter":"none", 24 | "exec-mode":"fork" 25 | }, 26 | { 27 | "name": "app-1", 28 | "cwd":"/home/meteor/build/bundle", 29 | "script": "main.js", 30 | "env": { 31 | "NODE_ENV":"production", 32 | "WORKER_ID":"0", 33 | "PORT":"5000", 34 | "ROOT_URL":"https://app.example.org", 35 | "MONGO_URL":"mongodb://localhost:27017/meteor", 36 | "MONGO_OPLOG_URL":"mongodb://localhost:27017/local", 37 | "HTTP_FORWARDED_COUNT":"1", 38 | "METEOR_SETTINGS": { } 39 | } 40 | }, 41 | { 42 | "name": "app-2", 43 | "cwd":"/home/meteor/build/bundle", 44 | "script": "main.js", 45 | "env": { 46 | "NODE_ENV":"production", 47 | "WORKER_ID":"1", 48 | "PORT":"5001", 49 | "ROOT_URL":"https://app.example.org", 50 | "MONGO_URL":"mongodb://localhost:27017/meteor", 51 | "MONGO_OPLOG_URL":"mongodb://localhost:27017/local", 52 | "HTTP_FORWARDED_COUNT":"1", 53 | "METEOR_SETTINGS": { } 54 | } 55 | }, 56 | { 57 | "name": "app-3", 58 | "cwd":"/home/meteor/build/bundle", 59 | "script": "main.js", 60 | "env": { 61 | "NODE_ENV":"production", 62 | "WORKER_ID":"2", 63 | "PORT":"5002", 64 | "ROOT_URL":"https://app.example.org", 65 | "MONGO_URL":"mongodb://localhost:27017/meteor", 66 | "MONGO_OPLOG_URL":"mongodb://localhost:27017/local", 67 | "HTTP_FORWARDED_COUNT":"1", 68 | "METEOR_SETTINGS": { } 69 | } 70 | } 71 | ] 72 | } 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | This is a series of scripts and files for production-grade deployment of Meteor. 3 | Please visit this forum thread to get the history: https://forums.meteor.com/t/deploying-to-aws-with-meteor-1-3-5-still-using-mup/27306/20 4 | 5 | ![PM2 Web client](https://github.com/ramezrafla/meteor-deployment/blob/master/screenshots/pm2.png?raw=true) 6 | 7 | # Warnings 8 | 1. First, this is for experienced system administrators. Please don't submit an issue asking 'How do I generate an SSH key'. 9 | 2. You should really look into using MDG's own hosting solution first. Only if you have different needs does it make sense to launch your own servers. 10 | 3. These scripts are sanitized versions of what we are currently using for our app (https://zeschool.zegenie.com), we will make every effort to keep up to date 11 | 4. Mongo (and Redis if you wnat to use redis-oplog https://github.com/cult-of-coders/redis-oplog) launched on the same server, disable in pm2.json / launch scripts if you are using external DB service 12 | 4. PR's are welcome 13 | 14 | # Setup 15 | We are using: 16 | 17 | 1. PM2 for all process management (see sample **pm2.json** which you have to start manually the first time on the server) -- and read PM2 docs for more info 18 | 2. tengine with load balancer and sticky sessions -- see our sample **nginx.conf** 19 | 3. Shell scripts to push build via SSH -- **remote-build.sh** 20 | 4. Shell scripts to install build remotely on server -- **deploy-build.sh** 21 | 5. Of course, nodejs / npm should be installed on your server (as well as pm2) -- and .sh in this repo are executable 22 | 6. Key-based SSH 23 | 7. Mongo and Redis (**pm2.json**) on the same server, allocate a process for Mongo 24 | 25 | # Folders 26 | 27 | On the server, 28 | 29 | 1. we assume the app will be started from /home/meteor/build 30 | 2. pm2.json and deploy-build.sh should be located in /home/meteor/scripts 31 | 32 | 33 | # Process 34 | 1. We compile on our dev machine 35 | 2. Push the tarball onto server 36 | 3. Decompress and install on server 37 | 4. Reload PM2 38 | 39 | All this is done by calling local script **./build-remote.sh** which takes care of everything (if you set things based on the folders above). 40 | 41 | # A note about tengine 42 | Tengine (http://tengine.taobao.org/documentation.html) is a fork of nginx but with solid production-grade features (that you would have to pay for with the pro version of nginx). nginx.conf in this repo contains our (sanitized) setup. 43 | 44 | # Bonus 45 | 1. You will notice in the nginx.conf we are accessing the pm2 web interface to get real time view of how our processes are running (Really cool!) -- see image at top of this readme 46 | 2. You will also notice in nginx.conf that status.example.org provides real-time view of the three meteor processes we have launched (load balancer status -- see image below) ![Status](https://github.com/ramezrafla/meteor-deployment/blob/master/screenshots/status.png?raw=true) 47 | 3. We use Icinga2 (you can use Nagios too) to monitor server health, including checking on the load balancer status and pm2 processes (not included yet) 48 | 4. We use Cloudfront CDN (see http://joshowens.me/using-a-cdn-with-your-production-meteor-app/ with some improvements) and this is reflected in the nginx.conf 49 | 50 | # Should I use Meteor-based load balancer / deployment 51 | 52 | Many in the community use the Cluster package for managing meteor instances. This is a **lousy** solution. The explanation of the authors is that HAProxy is too complex. Agreed, but nginx / tengine include proper load balancers. We shouldn't use meteor to run webserver functions. This looks like the classical dev thinking s/he is a system admin. 53 | 54 | I personally dislike mup and even more mupx. The latter uses docker. The approach suggested here is for truly production-grade deployments. This is such an important issue, that I believe a good company either outsources deployment / hosting (e.g. Galaxy) or does it right. 55 | -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | upstream meteor { 2 | session_sticky; 3 | server 127.0.0.1:5000 max_fails=2 fail_timeout=2s; 4 | server 127.0.0.1:5001 max_fails=2 fail_timeout=2s; 5 | server 127.0.0.1:5002 max_fails=2 fail_timeout=2s; 6 | check interval=1000 rise=2 fall=2 timeout=1000 type=http; 7 | } 8 | 9 | map $http_upgrade $connection_upgrade { 10 | default upgrade; 11 | '' close; 12 | } 13 | 14 | server { 15 | listen 80; 16 | server_name pm2.example.org; 17 | 18 | location / { 19 | allow __ip1__; 20 | allow __ip2__; 21 | deny all; 22 | access_log off; 23 | proxy_set_header Host $host; 24 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 25 | proxy_set_header X-Forwarded-Proto https; 26 | proxy_redirect off; 27 | proxy_pass http://127.0.0.1:9001; 28 | proxy_http_version 1.1; 29 | proxy_set_header Upgrade $http_upgrade; 30 | proxy_set_header Connection $connection_upgrade; 31 | proxy_max_temp_file_size 0; 32 | } 33 | } 34 | 35 | 36 | server { 37 | listen 80; 38 | server_name status.example.org; 39 | 40 | location / { 41 | check_status; 42 | allow __ip1__; 43 | allow __ip2__; 44 | deny all; 45 | access_log off; 46 | } 47 | 48 | } 49 | 50 | server { 51 | listen 443 default_server ssl; 52 | server_name app.example.org; 53 | 54 | ssl_certificate ../ssl/bundle-merged.crt; # full path to SSL certificate and CA certificate concatenated together 55 | ssl_certificate_key ../ssl/site_key.key; # full path to SSL key 56 | 57 | # performance enhancement for SSL 58 | ssl_stapling on; 59 | ssl_session_cache shared:SSL:10m; 60 | ssl_session_timeout 5m; 61 | 62 | # safety enhancement to SSL: make sure we actually use a safe cipher 63 | ssl_prefer_server_ciphers on; 64 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2; 65 | ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:ECDHE-RSA-RC4-SHA:ECDHE-ECDSA-RC4-SHA:RC4-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!3DES:!MD5:!PSK'; 66 | 67 | # config to enable HSTS(HTTP Strict Transport Security) https://developer.mozilla.org/en-US/docs/Security/HTTP_Strict_Transport_Security 68 | # to avoid ssl stripping https://en.wikipedia.org/wiki/SSL_stripping#SSL_stripping 69 | add_header Strict-Transport-Security "max-age=31536000;"; 70 | 71 | # If your application is not compatible with IE <= 10, this will redirect visitors to a page advising a browser update 72 | # This works because IE 11 does not present itself as MSIE anymore 73 | if ($http_user_agent ~ "MSIE" ) { 74 | return 303 https://browser-update.org/update.html; 75 | } 76 | 77 | try_files $uri/index.html $uri; 78 | 79 | # for cloudfront 80 | location ~* \.(eot|ttf|woff|woff2)$ { 81 | proxy_set_header Host $host; 82 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 83 | proxy_set_header X-Forwarded-Proto https; 84 | proxy_redirect off; 85 | proxy_pass http://meteor; 86 | proxy_http_version 1.1; 87 | proxy_set_header Upgrade $http_upgrade; 88 | proxy_set_header Connection $connection_upgrade; 89 | proxy_max_temp_file_size 0; 90 | add_header Access-Control-Allow-Origin *; 91 | add_header Vary Origin; 92 | add_header Pragma public; 93 | add_header Cache-Control "public"; 94 | expires max; 95 | } 96 | 97 | # for cloudfront 98 | location ~* "^/[a-z0-9]{40}\.(css|js)$" { 99 | proxy_set_header Host $host; 100 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 101 | proxy_set_header X-Forwarded-Proto https; 102 | proxy_redirect off; 103 | proxy_pass http://meteor; 104 | proxy_http_version 1.1; 105 | proxy_set_header Upgrade $http_upgrade; 106 | proxy_set_header Connection $connection_upgrade; 107 | proxy_max_temp_file_size 0; 108 | expires max; 109 | } 110 | 111 | location / { 112 | proxy_set_header Host $host; 113 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 114 | proxy_set_header X-Forwarded-Proto https; 115 | proxy_redirect off; 116 | proxy_pass http://meteor; 117 | proxy_http_version 1.1; 118 | proxy_set_header Upgrade $http_upgrade; 119 | proxy_set_header Connection $connection_upgrade; 120 | proxy_max_temp_file_size 0; 121 | } 122 | } 123 | --------------------------------------------------------------------------------