mongodb http://tech.oeru.org/ en Hourly versioned MongoDB backup http://tech.oeru.org/hourly-versioned-mongodb-backup <span class="field field--name-title field--type-string field--label-hidden">Hourly versioned MongoDB backup</span> <div class="field field-node--field-blog-tags field-name-field-blog-tags field-type-entity-reference field-label-above"> <h3 class="field__label">Blog tags</h3> <div class="field__items"> <div class="field__item field__item--mongodb"> <span class="field__item-wrapper"><a href="/taxonomy/term/14" hreflang="en">mongodb</a></span> </div> <div class="field__item field__item--backup"> <span class="field__item-wrapper"><a href="/taxonomy/term/57" hreflang="en">backup</a></span> </div> <div class="field__item field__item--docker"> <span class="field__item-wrapper"><a href="/taxonomy/term/16" hreflang="en">docker</a></span> </div> <div class="field__item field__item--docker-compose"> <span class="field__item-wrapper"><a href="/taxonomy/term/49" hreflang="en">docker-compose</a></span> </div> <div class="field__item field__item--bash"> <span class="field__item-wrapper"><a href="/taxonomy/term/58" hreflang="en">bash</a></span> </div> </div> </div> <span class="field field--name-uid field--type-entity-reference field--label-hidden"><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">dave</span></span> <span class="field field--name-created field--type-created field--label-hidden">Mon 29/04/2019 - 16:28</span> <div class="clearfix text-formatted field field-node--body field-name-body field-type-text-with-summary field-label-hidden"> <div class="field__items"> <div class="field__item"><p>Because the collaboration of an open community is its real history, I place a high value on backing up the Rocket.Chat servers I'm responsible for, and <em>especially</em> the data they generate, held in MongoDB files (on the host) managed by a MongoDB container.</p> <p>To do that reliably, I have set up <a href="https://git.oeru.org/oeru/mongobackup">a bash script</a> which does an hourly backup of all MongoDB "databases" and automatically maintains 24 hourly, 7 daily, 4 weekly, 12 monthly, and 7 yearly snapshots of the databases.</p> <p>Once you've configured your backup script properly, you should be able to run this command to do a backup...</p> <p><code>/etc/mongobackup/dbbackup-mongo --hourly</code></p> <p>easy-peasy.</p> </div> </div> </div> <section class="field field-node--field-blog-comments field-name-field-blog-comments field-type-comment field-label-above comment-wrapper"> <a name="comments"></a> <div class="comment-form-wrapper"> <h2 class="comment-form__title">Add new comment</h2> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=27&amp;2=field_blog_comments&amp;3=comment" token="a3hwmZDJbiAJ569rEIXJYUfTF3BaDQbUaOh1gRHfc5g"></drupal-render-placeholder> </div> </section> Mon, 29 Apr 2019 04:28:43 +0000 dave 27 at http://tech.oeru.org Upgrading RocketChat to 1.0.x and MongoDB to 4.0 http://tech.oeru.org/upgrading-rocketchat-10x-and-mongodb-40 <span class="field field--name-title field--type-string field--label-hidden">Upgrading RocketChat to 1.0.x and MongoDB to 4.0</span> <div class="field field-node--field-blog-tags field-name-field-blog-tags field-type-entity-reference field-label-above"> <h3 class="field__label">Blog tags</h3> <div class="field__items"> <div class="field__item field__item--rocketchat"> <span class="field__item-wrapper"><a href="/taxonomy/term/18" hreflang="en">rocket.chat</a></span> </div> <div class="field__item field__item--mongodb"> <span class="field__item-wrapper"><a href="/taxonomy/term/14" hreflang="en">mongodb</a></span> </div> <div class="field__item field__item--docker"> <span class="field__item-wrapper"><a href="/taxonomy/term/16" hreflang="en">docker</a></span> </div> <div class="field__item field__item--docker-compose"> <span class="field__item-wrapper"><a href="/taxonomy/term/49" hreflang="en">docker-compose</a></span> </div> <div class="field__item field__item--ubuntu-linux"> <span class="field__item-wrapper"><a href="/taxonomy/term/12" hreflang="en">ubuntu linux</a></span> </div> </div> </div> <span class="field field--name-uid field--type-entity-reference field--label-hidden"><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">dave</span></span> <span class="field field--name-created field--type-created field--label-hidden">Mon 29/04/2019 - 14:39</span> <div class="clearfix text-formatted field field-node--body field-name-body field-type-text-with-summary field-label-hidden"> <div class="field__items"> <div class="field__item"><p>With the recent release of Rocket.Chat 1.0.x (after a couple years undergoing development at a fairly blistering pace), it's time for many of us to upgrade!</p> <p>Previously, I showed how to <a href="/docker-compose-better-way-deploy-rocketchat-wekan-and-mongodb">install Rocket.Chat via Docker Compose</a> but that was a much earlier version of Rocket.Chat and version 3.4 of MongoDB, which is now quite old (by FOSS standards at least). And it turns out upgrading everything has a few gotchas, so here's how I managed to do it.</p> <p>Before you do <em>anything</em> <a href="/hourly-versioned-mongodb-backup">do a backup of your MongoDB</a>!</p> <p>The first thing you need to do is upgrade <em>the way</em> in which you're running MongoDB. You have to enable a capability called "Local Replication".</p> <h2>Update your Docker Compose configuration</h2> <p>My first step, after logging into my virtual machine via SSH as the <em>unprivileged user</em> that I created to run docker commands, was to update my <code>docker-compose.yml</code> file (if you followed my previous instructions, you'll find it in <code>/home/www/docker-rocketchat-wekan-mongo</code>). </p> <p>First, make a backup of it nearby...</p> <p>cd <code>/home/www/docker-rocketchat-wekan-mongo</code><br /> cp docker-compose.yml docker-compose.yml-mongo3.4</p> <p>and then edit the file to say this:</p> <p><code>version: '2'<br /> services:<br />   mongo:<br />     restart: unless-stopped<br />     image: mongo<strong>:3.4</strong><br />     volumes:<br />       - [data directory path]:/data/db<br />       - [backup directory path]:/backups<br />     command: --smallfiles <strong>--oplogSize 128 --replSet rs0</strong><br /><strong>  # this container's job is just run the command to initialize the replica set.<br />   # it will run the command and remove himself (it will not stay running)<br />   mongo-init-replica:<br />     image: mongo:3.4<br />     command: 'bash -c "for i in `seq 1 30`; do mongo mongo/rocketchat --eval \"rs.initiate({ _id: ''rs0'', members: [ { _id: 0, host: ''localhost:27017'' } ]})\" &amp;&amp; s=$$? &amp;&amp; break || s=$$?; echo \"Tried $$i times. Waiting 5 secs...\"; sleep 5; done; (exit $$s)"'<br />     depends_on:<br />       - mongo</strong><br />   rocketchat:<br />     restart: unless-stopped<br />     image: rocketchat/rocket.chat<strong>:latest</strong></code><br /><code><strong>    command: bash -c 'for i in `seq 1 30`; do node main.js &amp;&amp; s=$$? &amp;&amp; break || s=$$?; echo "Tried $$i times. Waiting 5 secs..."; sleep 5; done; (exit $$s)'</strong><br />     ports:<br />       - "127.0.0.1:[port number]:3000" # should be a free port above 1024<br />     depends_on:<br />       - mongo<br />     environment:<br />       - MONGO_URL=mongodb://mongo/rocket<br /><strong>      - MONGO_OPLOG_URL=mongodb://mongo/local</strong><br />       - ROOT_URL=[domain name (including schema, e.g. http://)]<br />     volumes:<br />       - [upload directory path]:/var/www/rocket.chat/uploads</code><br /><strong><code>    labels:<br />       - "traefik.backend=rocketchat"<br />       - "traefik.frontend.rule=Host: [your domain name (<em>not </em>including schema)]"</code></strong></p> <p>Now, having updated your docker-compose.yml file, you have to do a couple other things. To do the upgrade from MongoDB 3.4 to 4.0, you have to do the interim upgrade to 3.6 first.</p> <h2>Enabling Local Replication</h2> <p>First you need to check what version of MongoDB you're <em>currently</em> using - both the version you're running <em>and</em> the "Feature Compatibility Version" (you can run a newer version of MongoDB, but configure it to only run features from some previous version to avoid breaking older software that depends on old features)... Do this as follows.</p> <p>Access your MongoDB instance:</p> <p><code>docker-compose exec mongo bash</code></p> <p>That should give you a command prompt that looks like this:</p> <p><code>root@a56eefe9f352: # </code></p> <p>but the container identifier (after the @) will be different (but the same length). At that prompt, you can run this command:</p> <p><code>mongo --eval "db.adminCommand( { getParameter: 1, featureCompatibilityVersion: 1 } )"</code></p> <p>Tip: if you're on a Linux desktop, you can copy this command (via CTRL-C) from this document and past it into your SSH terminal window (via CTRL+SHIFT-V).</p> <p>It should tell you you're either running "featureCompatibilityVersion" 3.2 or 3.4. If it's the latter, skip this next step. If not, run this next:</p> <p><code>mongo --eval "db.adminCommand( { setFeatureCompatibilityVersion: '3.4' } )"</code></p> <p>to set the version to 3.4. If the command succeeds you'll likely see something like</p> <p><code>{ "ok" : 1 }</code></p> <p>as the response.</p> <p>Now you can upgrade your Mongo 3.4 is the latest version (should be 3.4.20 at the time of this writing). Get out of the container (back to your Docker host) via CTRL-D (or "exit" - they're synonymous for logging out of a terminal session). Then you can run:</p> <p><code>docker-compose pull mongo</code></p> <p>That should update both your Mongo docker container to the latest version in the 3.4 series.</p> <p>The final step to enabling local replication is to run</p> <p><code>docker-compose up -d mongo mongo-init-replica &amp;&amp; docker-compose logs -f</code></p> <p>That will restart MongoDB and drop you into the stream of logging from all the containers (including the rocket.chat container). It'll also start the "mongo-init-replica" container.  That container should run briefly <em>and then exit cleanly</em> having set up the local replication that you'll need for subsequent upgrades to MongoDB!</p> <p>Check for any errors in the output... there might be a couple if it takes your MongoDB a bit of time to accept connections... as long as it eventually stops showing errors, you should be ok!</p> <p> </p> <h2>Upgrading Rocket.Chat to 1.0.x</h2> <p>Now that you're fully on version 3.4, running in local replica mode, you can update your Rocket.Chat instance.   Rocket.Chat still supports Mongo 3.4 (it won't for long, thus this tutorial!), so you can now upgrade the Rocket.Chat container as well as make sure your Mongo 3.4 is the latest version (should be 3.4.20 at the time of this writing).</p> <p>Note that the latest version of the Rocket.Chat docker container could be quite a lot higher when you read this... if it's beyond, say, 1.1 it might be unsafe to use the approach I'm describing. You can <a href="https://github.com/RocketChat/Rocket.Chat/releases">check the current version release status</a>. To protect yourself, you can alter the rocket.chat image line in your docker-compose.yml file to explicitly tell it to use the 1.0.x series for which these instructions should continue to apply... pick the highest 1.0.x version you can find in the releases and alter the line in docker-compose.yml to specify that version:</p> <p><code>    image: rocketchat/rocket.chat<strong>:1.0.1</strong></code></p> <p>or whatever the latest 1.0.x version is. Then you can run:</p> <p><code>docker-compose pull</code> rocketchat</p> <p>which will update your Rocket.Chat from the current version to the one specified.</p> <p>Then restart your Rocket.Chat instance:</p> <p><code>docker-compose up -d &amp;&amp; docker-compose logs -f</code></p> <p>That should restart both MongoDB and Rocket.Chat, and drop you into the stream of logging from both containers. It'll also start the "mongo-init-replica" container again, but having done its job it should exit happily again.</p> <p>Check for any errors in the output... there might be a couple if it takes your MongoDB a bit of time to accept connections... as long as it eventually stops showing errors, you should be ok! Eventually, you should see something similar to (with version details updated appropriately):</p> <p> </p> <p><code>+----------------------------------------------+<br /> |                SERVER RUNNING                |<br /> +----------------------------------------------+<br /> |                                              |<br /> |  Rocket.Chat Version: 1.0.1                  |<br /> |       NodeJS Version: 8.11.4 - x64           |<br /> |      MongoDB Version: 3.4.20                 |<br /> |       MongoDB Engine: wiredTiger             |<br /> |             Platform: linux                  |<br /> |         Process Port: 3000                   |<br /> |             Site URL: https://chat.oeru.org  |<br /> |     ReplicaSet OpLog: Enabled                |<br /> |          Commit Hash: 60f1a4afd6             |<br /> |        Commit Branch: HEAD                   |<br /> |                                              |<br /> +----------------------------------------------+</code></p> <p>Your instance is now running the right version! Time to tidy things up by upgrading Mongo the rest of the way to 4.0!</p> <p> </p> <h2>Upgrading to MongoDB 3.6</h2> <p>Now you can upgrade Mongo to 3.6. First, adjust your docker-compose.yml file.  Update both occurances of this line:</p> <p><code>image: mongo:3.4</code></p> <p>to</p> <p><code>image: mongo:3.6</code></p> <p>Then you can do another</p> <p><code>docker-compose pull mongo</code></p> <p>which will download the newer Mongo 3.6 docker container. Then you can again run</p> <p><code>docker-compose up -d &amp;&amp; docker-compose logs -f</code></p> <p>Again check for errors. If there are none (other than perhaps a brief set of "mongo is not accepting connections" errors), you should be fine to update the "compatibility version" from 3.4 to 3.6... Get a session on your Mongo container via</p> <p><code>docker-compose exec mongo bash</code></p> <p>and then (as above) run this:</p> <p><code>mongo --eval "db.adminCommand( { setFeatureCompatibilityVersion: '3.6' } )"</code></p> <p>which should give you a more complicated response than that for the 3.4 transition, but it should still more or less say "ok"... To make sure everything's happy with the change, it's wise to run</p> <p><code>docker-compose up -d &amp;&amp; docker-compose logs -f</code></p> <p>again and make sure there're no obvious errors. If not (after making another database backup for safety!!) we can proceed to Mongo 4.0!</p> <h2>Final push to MongoDB 4.0</h2> <p>Finally, you can again edit your docker-compose.yml and change both occurrences of</p> <p> </p> <p><code>image: mongo:3.6</code></p> <p>to</p> <p><code>image: mongo:4.0</code></p> <p>Then you can do a final</p> <p><code>docker-compose pull mongo</code></p> <p>which will download the newer Mongo 4.0 docker container. Then you can again run</p> <p><code>docker-compose up -d &amp;&amp; docker-compose logs -f</code></p> <p>And, assuming you don't see any errors, you can push the feature compatibility to 4.0:</p> <p><code>docker-compose exec mongo bash</code></p> <p>and then run:</p> <p><code>mongo --eval "db.adminCommand( { setFeatureCompatibilityVersion: '4.0' } )"</code></p> <p>followed by a final</p> <p><code>docker-compose up -d &amp;&amp; docker-compose logs -f</code></p> <p>And, again, if you don't see any errors... you should get something a bit like this:</p> <p><code>+----------------------------------------------+<br /> |                SERVER RUNNING                |<br /> +----------------------------------------------+<br /> |                                              |<br /> |  Rocket.Chat Version: 1.0.1                  |<br /> |       NodeJS Version: 8.11.4 - x64           |<br /> |      MongoDB Version: 4.0.9                  |<br /> |       MongoDB Engine: wiredTiger             |<br /> |             Platform: linux                  |<br /> |         Process Port: 3000                   |<br /> |             Site URL: https://chat.oeru.org  |<br /> |     ReplicaSet OpLog: Enabled                |<br /> |          Commit Hash: 60f1a4afd6             |<br /> |        Commit Branch: HEAD                   |<br /> |                                              |<br /> +----------------------------------------------+</code></p> <p>you're done and future proofed!</p> </div> </div> </div> <section class="field field-node--field-blog-comments field-name-field-blog-comments field-type-comment field-label-above comment-wrapper"> <a name="comments"></a> <div class="comment-form-wrapper"> <h2 class="comment-form__title">Add new comment</h2> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=26&amp;2=field_blog_comments&amp;3=comment" token="VK6tqiyjVB7HYLx9xMKgF1KeXWiFxiQ-XNY71DIaaF0"></drupal-render-placeholder> </div> </section> Mon, 29 Apr 2019 02:39:33 +0000 dave 26 at http://tech.oeru.org Docker Compose: A better way to deploy Rocketchat, Wekan, and MongoDB http://tech.oeru.org/docker-compose-better-way-deploy-rocketchat-wekan-and-mongodb <span class="field field--name-title field--type-string field--label-hidden">Docker Compose: A better way to deploy Rocketchat, Wekan, and MongoDB</span> <div class="field field-node--field-blog-tags field-name-field-blog-tags field-type-entity-reference field-label-above"> <h3 class="field__label">Blog tags</h3> <div class="field__items"> <div class="field__item field__item--docker-compose"> <span class="field__item-wrapper"><a href="/taxonomy/term/49" hreflang="en">docker-compose</a></span> </div> <div class="field__item field__item--rocketchat"> <span class="field__item-wrapper"><a href="/taxonomy/term/18" hreflang="en">rocket.chat</a></span> </div> <div class="field__item field__item--wekan"> <span class="field__item-wrapper"><a href="/taxonomy/term/15" hreflang="en">wekan</a></span> </div> <div class="field__item field__item--ubuntu-linux"> <span class="field__item-wrapper"><a href="/taxonomy/term/12" hreflang="en">ubuntu linux</a></span> </div> <div class="field__item field__item--_604"> <span class="field__item-wrapper"><a href="/taxonomy/term/27" hreflang="en">16.04</a></span> </div> <div class="field__item field__item--docker"> <span class="field__item-wrapper"><a href="/taxonomy/term/16" hreflang="en">docker</a></span> </div> <div class="field__item field__item--mongodb"> <span class="field__item-wrapper"><a href="/taxonomy/term/14" hreflang="en">mongodb</a></span> </div> <div class="field__item field__item--nginx"> <span class="field__item-wrapper"><a href="/taxonomy/term/30" hreflang="en">nginx</a></span> </div> </div> </div> <span class="field field--name-uid field--type-entity-reference field--label-hidden"><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">dave</span></span> <span class="field field--name-created field--type-created field--label-hidden">Tue 23/05/2017 - 11:03</span> <div class="clearfix text-formatted field field-node--body field-name-body field-type-text-with-summary field-label-hidden"> <div class="field__items"> <div class="field__item"><p>A few months back, I posted instructions on deploying <a href="/installing-rocketchat-docker-ubuntu-linux-1404">Rocket.Chat</a> and <a href="/installing-wekan-docker-ubuntu-linux-1404">Wekan</a> instances (and their mutual dependency, <a href="/installing-mongodb-docker-ubuntu-linux-1404">MongoDB</a>) individually. Since then, I've spent some time with Docker Compose, a set of scripts which help you to define, build, and manage a set of Docker containers. Docker Compose is a thing of beauty. This is the way I now deploy Rocket.Chat, Wekan, and MongoDB together.</p> <h2>Install Docker and Docker Compose</h2> <p>Install <a href="https://docs.docker.com/engine/installation/linux/ubuntu/">Docker</a> (including the "<a href="https://docs.docker.com/engine/installation/linux/linux-postinstall/">post-installation</a>" steps to allow non-root users to run Docker) and <a href="https://docs.docker.com/compose/install/#alternative-install-options" title="We recommend the &quot;pip&quot; install method">Docker Compose</a> on your server (we recommend Ubuntu 16.04 or the older 14.04). We recommend using the "pip" (Python package manager) to do the install.</p> <h2>Create your Docker Compose recipe</h2> <p>We recommend creating a directory with an obvious name - in my case, it's <code>/home/www/docker-rocketchat-wekan-mongo</code></p> <p>In that directory, I create a file called <code>docker-compose.yml</code> containing (I've removed implementation specific details and replaced them with [placeholders]):</p> <p><code>version: '2'<br /> services:<br />   mongo:<br />     restart: unless-stopped<br />     image: mongo<br />     volumes:<br />       - [data directory path]:/data/db<br />       - [backup directory path]:/backups<br />     command: --smallfiles<br />   rocketchat-oeru:<br />     restart: unless-stopped<br />     image: rocketchat/rocket.chat<br />     ports:<br />       - "127.0.0.1:[port number]:3000" # should be a free port above 1024<br />     depends_on:<br />       - mongo<br />     environment:<br />       - MONGO_URL=mongodb://mongo/rocket<br />       - ROOT_URL=[domain name (including schema, e.g. http://)]<br />     volumes:<br />       - [upload directory path]:/var/www/rocket.chat/uploads<br />   wekan:<br />     restart: unless-stopped<br />     image: mquandalle/wekan<br />     ports:<br />       - "127.0.0.1:[port number]:80" # should be a free port above 1024<br />     depends_on:<br />       - mongo<br />     environment:<br />       - VIRTUAL_HOST=[domain name (don't include schema, e.g. https://)]<br />       - MONGO_URL=mongodb://mongo/plan<br />       - ROOT_URL=[domain name (include schema, e.g. https://)]<br />       - MAIL_URL=smtp://[smtp username]:[smtp password]@[server name or IP]:[port: 25, 465, or 587]/<br />     volumes:<br />       - [path to public content]:/built_app/programs/web.browser/app</code></p> <p>Note, you can include multiple instances of either Rocket.Chat or Wekan simply by providing a new name (e.g. rocketchat2 or wekan2 or similar) and a new set of properties - just make sure you're using a unique (and otherwise unused) port number! You can check what's on your server's ports using <code>netstat -punta | less </code>to make sure you're not doubling up. </p> <p>In case it's not obvious, you can leave out either the rocketchat or wekan definitions if you don't want to run those services!</p> <h2>Creating and Running the Docker Containers</h2> <p>It's easy to create the containers: simply run</p> <p><code>docker pull mongo<br /> docker pull rocket.chat<br /> docker pull mquandalle/wekan</code></p> <p>and when it's finished, run</p> <p><code>docker-compose up </code></p> <p>which should start all your containers, but leave you with a running log - this is great for testing, but when you're happy it's all running you hit CTRL-C (to shut down the current set of containers) and then run</p> <p><code>docker-compose up -d </code></p> <p>which runs the containers in daemon mode, without the running log. You can then log out of your server, and your containers will continue running!</p> <p>Based on the configuration above (the "unless-stopped" directive), your containers will restart automatically if your server is rebooted. If you <em>do</em> want to stop them for some reason, you can via</p> <p><code>docker-compose stop</code></p> <p>Easy.</p> <h2>Serving them to the Web</h2> <p>Once you've got your containers running, you need to make sure you've got a web server running on your host to act as the reverse proxy so that external requests get sent to them reliably! We use <a href="nginx.org">Nginx</a>.</p> <h3>RocketChat Nginx</h3> <p>Here's our configuration (with appropriate [substitutions]) - you can create it as <code>/etc/nginx/sites-available/[domain name]</code>:</p> <p><code>server {<br />     listen 80;<br />     server_name [domain name];</code></p> <p><code>  ## Access and error logs.<br />   access_log /var/log/nginx/[domain name]_access.log;<br />   error_log /var/log/nginx/[domain name]_error.log;</code></p> <p><code>  # see https://tech.oeru.org/protecting-your-users-lets-encrypt-ssl-certs<br />   include /etc/nginx/includes/letsencrypt.conf</code></p> <p><code>  # we use a 302 temporary redirect rather than a 301 permanent redir</code><br /><code>  location / {<br />     return 302 https://[domain name]$request_uri;<br />   }<br /> }<br /><br /> server {<br />     listen 443 ssl;<br />     ssl on;</code><br />      <code>  # see https://tech.oeru.org/protecting-your-users-lets-encrypt-ssl-certs<br />     ssl_certificate /etc/letsencrypt/live/[domain name]/fullchain.pem;<br />     ssl_certificate_key /etc/letsencrypt/live/[domain name]/privkey.pem;<br />     ssl_protocols TLSv1 TLSv1.1 TLSv1.2;<br />     ssl_dhparam /etc/ssl/certs/dhparam.pem;</code></p> <p><code>    keepalive_timeout 20s;</code></p> <p><code>    root /var/www/html;<br />     index index.html index.htm;</code></p> <p><code>    server_name [domain name];</code></p> <p><code>    ## Access and error logs.<br />     access_log /var/log/nginx/[domain name]_access.log;<br />     error_log /var/log/nginx/[domain name]_error.log;</code></p> <p><code>    location / {<br />         proxy_pass http://127.0.0.1:[your rocketchat port];<br />         proxy_http_version 1.1;<br />         proxy_set_header Upgrade $http_upgrade;<br />         proxy_set_header Connection "upgrade";<br />         proxy_set_header Host $http_host;<br />         proxy_set_header X-Forwarded-Host $host;<br />         proxy_set_header X-Real-IP $remote_addr;<br />         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;<br />         proxy_set_header X-Forward-Proto http;<br />         proxy_set_header X-Nginx-Proxy true;<br />         proxy_redirect off;<br />     }<br /> }</code></p> <p> </p> <h3>Wekan Nginx</h3> <p>Here's our configuration (with appropriate [substitutions]) - you can create it as <code>/etc/nginx/sites-available/[domain name]</code>: </p> <p><code># from https://github.com/wekan/wekan/wiki/Install-Wekan-Docker-in-production<br /> upstream websocket {<br />     server 127.0.0.1:[wekan port];<br /> }</code></p> <p><code>map $http_upgrade $connection_upgrade {<br />     default upgrade;<br />     '' close;<br /> }</code><br /><br /><code>server {<br />     listen    80;<br /><br />     root /var/www/html;<br />     index index.html index.htm;</code></p> <p><code>    # Make site accessible from http://localhost/<br />     server_name [domain name];</code></p> <p><code>    access_log /var/log/nginx/[domain name]_access.log;<br />     error_log /var/log/nginx/[domain name]_error.log;</code></p> <p><code>    # see https://tech.oeru.org/protecting-your-users-lets-encrypt-ssl-certs<br />     include /etc/nginx/includes/letsencrypt.conf</code></p> <p><code>    location / {<br />         return 302 https://[domain name]$request_uri; <br />     }<br /> }</code></p> <p><code>server {<br />     listen 443 ssl;<br />     ssl on;</code><br />      <code>  # see https://tech.oeru.org/protecting-your-users-lets-encrypt-ssl-certs<br />     ssl_certificate /etc/letsencrypt/live/[domain name]/fullchain.pem;<br />     ssl_certificate_key /etc/letsencrypt/live/[domain name]/privkey.pem;<br />     ssl_protocols TLSv1 TLSv1.1 TLSv1.2;<br />     ssl_dhparam /etc/ssl/certs/dhparam.pem;</code></p> <p><code>    keepalive_timeout 20s;</code></p> <p><code>    access_log /var/log/nginx/[domain name]_access.log;<br />     error_log /var/log/nginx/[domain name]_error.log;</code></p> <p><code>    root /var/www/html;<br />     index index.html index.htm;</code></p> <p><code>    server_name [domain name];</code></p> <p><code>    location / {<br />         proxy_read_timeout 300;<br />         proxy_connect_timeout 300;<br />         proxy_redirect off;<br />         proxy_set_header Host $http_host;<br />         proxy_set_header X-Real-IP $remote_addr;<br />         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;<br />         proxy_set_header X-Forwarded-Proto scheme;<br />         proxy_pass http://127.0.0.1:[your wekan port];<br />         proxy_set_header Host $host;<br />     }</code></p> <p><code>    location ~ websocket$ {<br />         proxy_pass http://websocket;<br />         proxy_http_version 1.1;<br />         proxy_set_header Upgrade $http_upgrade;<br />         proxy_set_header Connection $connection_upgrade;<br />     }</code><br /><code>}</code></p> <h3>Enable Nginx Configuration</h3> <p>To make your configurations active, do the following for each of your Nginx configurations:</p> <p><code>cd /etc/nginx/sites-enabled</code></p> <p>Do this for each file:<br /><code>ln -sf ../sites-available/[filename] .</code></p> <p>To check if there are any errors in the files, run</p> <p><code>nginx -t</code></p> <p>If not, you can restart Nginx to incorporate the new configuration files:</p> <p><code>sudo service nginx reload</code></p> <p>You can check for errors in the relevant log files specified in your nginx configurations above in <code>/var/log/nginx/*_error.log</code> or <code>/var/log/nginx/*_access.log</code>.</p> <h2>Protecting your users and reputation with encryption</h2> <p>We encourage you to ensure that these services are made available with full encryption to protect your users' privacy. It's <a href="/protecting-your-users-lets-encrypt-ssl-certs">easy (and no cost) to set up</a>!  The "include" directive in the Nginx configuration files above are examples of this approach.</p> <h2>Upgrades and Backups</h2> <p>We also encourage you to keep your services upgraded. It's easy to do and you'll experience little if any perceptible down time!</p> <p>Simply re-pull the containers and restart them - the updated containers will be launched without loss of data!</p> <p><code>docker pull mongo<br /> docker pull rocket.chat<br /> docker pull mquandalle/wekan</code></p> <p><code>docker-compose up -d</code></p> <p>If you want to back up your data - you need to do normal file backups of the directories on your local server that you've configured in the <code>docker-compose.yml</code> file, and you can do MongoDB backups based on <a href="/installing-mongodb-docker-ubuntu-linux-1404">our previous article</a> on the topic!</p> </div> </div> </div> <section class="field field-node--field-blog-comments field-name-field-blog-comments field-type-comment field-label-above comment-wrapper"> <a name="comments"></a> <div class="comment-form-wrapper"> <h2 class="comment-form__title">Add new comment</h2> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=13&amp;2=field_blog_comments&amp;3=comment" token="IKqqNGzna0isfyEknk_oRE2sfWThSeFDbglHdf4AmjA"></drupal-render-placeholder> </div> </section> Mon, 22 May 2017 23:03:29 +0000 dave 13 at http://tech.oeru.org Installing Rocket.Chat with Docker on Ubuntu Linux 14.04 http://tech.oeru.org/installing-rocketchat-docker-ubuntu-linux-1404 <span class="field field--name-title field--type-string field--label-hidden">Installing Rocket.Chat with Docker on Ubuntu Linux 14.04</span> <div class="field field-node--field-blog-tags field-name-field-blog-tags field-type-entity-reference field-label-above"> <h3 class="field__label">Blog tags</h3> <div class="field__items"> <div class="field__item field__item--install"> <span class="field__item-wrapper"><a href="/taxonomy/term/11" hreflang="en">install</a></span> </div> <div class="field__item field__item--ubuntu-linux"> <span class="field__item-wrapper"><a href="/taxonomy/term/12" hreflang="en">ubuntu linux</a></span> </div> <div class="field__item field__item--_404"> <span class="field__item-wrapper"><a href="/taxonomy/term/13" hreflang="en">14.04</a></span> </div> <div class="field__item field__item--docker"> <span class="field__item-wrapper"><a href="/taxonomy/term/16" hreflang="en">docker</a></span> </div> <div class="field__item field__item--rocketchat"> <span class="field__item-wrapper"><a href="/taxonomy/term/18" hreflang="en">rocket.chat</a></span> </div> <div class="field__item field__item--lets-encrypt"> <span class="field__item-wrapper"><a href="/taxonomy/term/17" hreflang="en">let&#039;s encrypt</a></span> </div> <div class="field__item field__item--mongodb"> <span class="field__item-wrapper"><a href="/taxonomy/term/14" hreflang="en">mongodb</a></span> </div> </div> </div> <span class="field field--name-uid field--type-entity-reference field--label-hidden"><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">dave</span></span> <span class="field field--name-created field--type-created field--label-hidden">Thu 24/11/2016 - 10:59</span> <div class="field field-node--field-image field-name-field-image field-type-image field-label-hidden has-multiple"> <figure class="field-type-image__figure image-count-1"> <div class="field-type-image__item"> <a href="http://tech.oeru.org/sites/default/files/styles/max_1300x1300/public/2016-11/RocketChat_0.png?itok=6K_Ym-ms" title="The Rocket.Chat desktop app showing a typical conversation thread." data-colorbox-gallery="gallery-field_image-BePHkaTs_r0" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;The Rocket.Chat desktop app showing a typical conversation thread.&quot;}"><img src="/sites/default/files/styles/medium/public/2016-11/RocketChat_0.png?itok=LfyOwZNW" width="220" height="168" alt="The Rocket.Chat desktop app showing a typical conversation thread." typeof="foaf:Image" class="image-style-medium" /> </a> </div> </figure> <figure class="field-type-image__figure image-count-2"> <div class="field-type-image__item"> <a href="http://tech.oeru.org/sites/default/files/styles/max_1300x1300/public/2016-11/RocketChat2.png?itok=DkgkxTvj" title="A glimps of Rocket.Chat&#039;s adminisrative power and flexibility." data-colorbox-gallery="gallery-field_image-BePHkaTs_r0" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;A glimps of Rocket.Chat&#039;s adminisrative power and flexibility.&quot;}"><img src="/sites/default/files/styles/medium/public/2016-11/RocketChat2.png?itok=fT_PGRZH" width="220" height="169" alt="A glimps of Rocket.Chat&#039;s adminisrative power and flexibility." typeof="foaf:Image" class="image-style-medium" /> </a> </div> </figure> <figure class="field-type-image__figure image-count-3"> <div class="field-type-image__item"> <a href="http://tech.oeru.org/sites/default/files/styles/max_1300x1300/public/2016-11/RocketChat3_0.png?itok=FDIRKt6_" title="An example of editing a post and other post-level tools." data-colorbox-gallery="gallery-field_image-BePHkaTs_r0" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;An example of editing a post and other post-level tools.&quot;}"><img src="/sites/default/files/styles/medium/public/2016-11/RocketChat3_0.png?itok=9I8anoyq" width="220" height="169" alt="An example of editing a post and other post-level tools." typeof="foaf:Image" class="image-style-medium" /> </a> </div> </figure> </div> <div class="clearfix text-formatted field field-node--body field-name-body field-type-text-with-summary field-label-hidden"> <div class="field__items"> <div class="field__item"><p><a href="http://rocket.chat" title="The ROcket.Chat main site">Rocket.Chat</a> is a modern, open source messaging application which is functionally similar to a popular (and heavily marketed) proprietary tool called Slack. Rocket.Chat is built on a powerful open source real-time collaboration platform called <a href="https://www.meteor.com/" title="Open source state-based collaborative app building framework, written in Javascript">Meteor</a> (which, in turn, is built on the <a href="https://nodejs.org">Node.JS</a> open source framework), which supports real-time collaborative applications. The real-time collaboration means that if multiple people are using an app at the same time, even if they're spread across the world, they'll see the changes others are making in real-time.</p> <p>The OERu <a href="https://chat.oeru.org">has a Rocket.Chat instance</a> that has been getting very positive feedback from our Open Education Resource designers and collaborators, who use it to communicate with us at the OER Foundation, and with their fellow collaborators. There are currently about 20 channels to which users can subscribe and in which they can participate, dedicated to different topics of discussion.</p> <p>Rocket.Chat instances store their data in another open source tool, a key-value storage engine called <a href="https://www.mongodb.com/">MongoDB</a> - these instructions assume that you have already got a running MongoDB installed, and to facilitate that, we've provided this <a href="/node/3">handy MongoDB install guide</a> as a companion.</p> <p>This guide will cover both configuring and launching a Docker container with a working instance of Rocket.Chat. It will, in turn, be linked to another Docker container running MongoDB, and both will be capable of sending email via external authenticating email server. External user access to Rocket.Chat is provided by the Nginx web server (as a forward proxy). User interaction with Rocket.Chat is (and should always be) encrypted via recognised SSL certificates using the brilliant (and gratis) <a href="http://letsencrypt.org">Let's Encrypt</a> service.</p> <p>This instruction set assumes that you have command-line access (via SSH, in most cases) to your server, running Ubuntu Linux 14.04, probably a Virtual Machine hosted in a data centre somewhere - a very inexpensive way to do this is, for example, via <a href="https://digitalocean.com">DigitalOcean</a> (for the record, we have no relationship with DigitalOcean, I simply have substantial experience with their services), but there are many many options worldwide. These instructions will need to be modified slightly for other versions of Linux (e.g. Debian 8 or Ubuntu 16.04 or CentOS or others), but should be mostly valid. We'd be grateful to hear about anyone else's experiences in the comments below!</p> <h2>Installing Docker</h2> <p>See our instructions in the <a href="/node/3">MongoDB blog post</a>.</p> <h2>Installing Rocket.Chat</h2> <p>First, we would normally create a rocketchat user to create a place for any persistent data required for the app:</p> <p><code>sudo adduser rocketchat</code></p> <p>Although Rocket.Chat stores almost everything in the MongoDB you've already set up, it can store uploaded files in a designated directory so they survive updates to the Docker image:</p> <p><code>sudo -u rocketchat mkdir /home/rocketchat/uploads</code></p> <p>Assuming you've got Docker installed properly, to install the official Rocket.Chat Docker image, you can just run:</p> <p><code>docker pull rocketchat/rocket.chat</code></p> <p>Then you can launch it by running this by first launching your MongoDB container, and then running this (assumes you've named your MongoDB instance "mongodb" as per our <a href="/node/3">instructions</a>):</p> <p><code>docker run -d --name rocketchat -p 8051:3000 \<br />          -h [your_RocketChat_domain] \<br />          -v /home/rocketchat/uploads:</code><code>/var/www/rocket.chat/uploads \<br />          -e "MAIL_URL=[outgoing_mail_server]" \<br />          --link mongodb:mongo -e "MONGO_URL=mongodb://mongo/chat" \<br />          -e "ROOT_URL=http://[your_RocketChat_domain]" \<br />          --restart unless-stopped rocketchat/rocket.chat</code></p> <p><strong>Note: Please copy and paste the exact text of your "docker run" command into a reference file (I usually have a "README.oeru" reference file in the home directory of each Docker-based app I run)  as you will want to refer to it when doing upgrades!</strong></p> <p>You'll need to make sure you set appropriate values for [your_RocketChat_domain] and details for an SMTP (outgoing mail) server, because Rocket.Chat needs to send emails related to things like account registration, forgotten passwords, and configurable notifications, for example to alert you that you've been mentioned in discussions.</p> <p>You can use either a local mail server on the Docker host, in which case you'd put the local IP address of your server, as it would be seen by the Docker container, so 172.17.42.1 is the default IP of a Docker host. You could also use an authenticating SMTP server, and specify the details like this: <code>smtp://[username]:[password]@[IP-or-domainname]:[port, usually 25, 465, or 587].</code> Here's a what a made-up example might look like:</p> <p><code>-e "MAIL_URL=smtp://smtpmail:blahdiblah88@mail.oeru.org:25"</code></p> <p>Note, the <code>--restart unless-stopped</code> will ensure that this container is restarted on a reboot unless it's explicitly stopped, like via a <code>docker stop rocketchat</code>, like you might to update the Docker container.</p> <h2>Setting up Nginx as a proxy server</h2> <p>Having set up the Docker container, which is listening on port 8051 on the Docker container's <em>host</em>, you'll need to set up a reverse proxy on that host to make the site visible to the broader internet on your public IP address (you'll want to make sure the domain you specified above points to that IP address or is a CNAME to a domain that does).</p> <p>Once you've got that, you can proceed. In /etc/nginx/sites-available, I create a file called plan (as we use plan.oeru.org as our domain). Note, I use "vim" as my text editor. If you don't know it, perhaps use "nano" instead. It's much less powerful, but easier to use...</p> <p><code>sudo vim /etc/nginx/sites-available/chat</code></p> <p>Here're the contents - you'll want to change the domain name to suit your own choices. Similarly the names of SSL files and logs. The following file has a chunk in the middle commented out with "#s". More on that below:</p> <p><code>server {</code></p> <p><code>        listen 80; # this is one of our external IPs on the server.<br />         #listen   [::]:80 default ipv6only=on; ## listen for ipv6<br /><br />         root /usr/share/nginx/www;<br />         index index.html index.htm;<br /><br />         server_name chat.oeru.org;<br /><br />         access_log /var/log/nginx/chat.oeru.org_access.log;<br />         error_log /var/log/nginx/chat.oeru.org_error.log;<br /><br />         location /.well-known {<br />                 root /var/www/html;<br />                 default_type text/plain;<br />         }<br /><br /> #        location / {<br /> #                return 301 https://chat.oeru.org$request_uri;<br /> #        }       <br /> #}<br /> #<br /> #server {<br /> #        listen 443 ssl;<br /> #        ssl on;<br /> #        ssl_certificate /etc/letsencrypt/live/chat.oeru.org/fullchain.pem;<br /> #        ssl_certificate_key /etc/letsencrypt/live/chat.oeru.org/privkey.pem;</code><br /><code># </code><code>       ssl_protocols TLSv1 TLSv1.1 TLSv1.2;<br /> #        ssl_dhparam /etc/ssl/certs/dhparam.pem;<br /> #        keepalive_timeout 20s;<br /> #<br /> #        access_log /var/log/nginx/chat.oeru.org_access.log;<br /> #        error_log /var/log/nginx/chat.oeru.org_error.log;</code><br /><code>#        root /var/www/html;<br /> #        index index.html index.htm;</code><br /><code>#        server_name chat.oeru.org;<br /> #<br /> #        location /.well-known {<br /> #                root /var/www/html;<br /> #                default_type text/plain;<br /> #        }</code></p> <p><code>        location / {<br />                 proxy_read_timeout      300;<br />                 proxy_connect_timeout   300;<br />                 proxy_redirect          off;<br />                 proxy_set_header    Host                $http_host;<br />                 proxy_set_header    X-Real-IP           $remote_addr;<br />                 proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for;<br />                 proxy_set_header    X-Forwarded-Proto   $scheme;<br />                 proxy_pass      http://127.0.0.1:8081;<br />         }</code><br /><code>}</code></p> <p>When you've set up the file, you can enable it:</p> <p><code>sudo ln -sf /etc/nginx/sites-available/chat /etc/nginx/sites-enabled</code></p> <p>Test the file to ensure there aren't any syntax errors before reloading nginx:</p> <p><code>sudo nginx -t</code></p> <p>If this shows an error, you'll need to fix the file. If all's well, reload nginx to include the new configuration:</p> <p><code>sudo service nginx reload</code></p> <p>You should now be able to point your browser at your domain name, and you should get your Rocket.Chat site via the HTTP (not encrypted) protocol.</p> <p>A word to the wise - if it doesn't work, check your firewall settings!</p> <p>In the next step, we'll sort out your SSL certificate from <a href="https://letsencrypt.org/" title="Let's Encrypt - libre and gratis SSL certificates">Let's Encrypt</a>.</p> <h2>Protecting your users</h2> <p>Have a look at our <a href="/protecting-your-users-lets-encrypt-ssl-certs">Let's Encrypt howto</a>.</p> <h2>Upgrading Rocket.Chat</h2> <p>You should periodically upgrade Rocket.Chat, say every couple months, to benefit from improved features... The upgrade process usually means a few minutes of down time for the site, so pick a time when it isn't likely to be heavily used... You'll normally want to upgrade any dependent Docker containers at the same time (like <a href="/node/3">your MongoDB</a> and <a href="/node/4">Wekan</a>) so we have a <a href="/node/6">dedicated Upgrade article</a> for that.</p> </div> </div> </div> <section class="field field-node--field-blog-comments field-name-field-blog-comments field-type-comment field-label-above comment-wrapper"> <a name="comments"></a> <div class="comment-form-wrapper"> <h2 class="comment-form__title">Add new comment</h2> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=5&amp;2=field_blog_comments&amp;3=comment" token="y1jeKMWHoaChSEAGVwc5uZ-gT13uvOFBkFRfOSdeB9g"></drupal-render-placeholder> </div> </section> Wed, 23 Nov 2016 21:59:47 +0000 dave 5 at http://tech.oeru.org Upgrading your Docker Apps: MongoDB, Wekan, and Rocket.Chat http://tech.oeru.org/node/6 <span class="field field--name-title field--type-string field--label-hidden">Upgrading your Docker Apps: MongoDB, Wekan, and Rocket.Chat</span> <div class="field field-node--field-blog-tags field-name-field-blog-tags field-type-entity-reference field-label-above"> <h3 class="field__label">Blog tags</h3> <div class="field__items"> <div class="field__item field__item--wekan"> <span class="field__item-wrapper"><a href="/taxonomy/term/15" hreflang="en">wekan</a></span> </div> <div class="field__item field__item--rocketchat"> <span class="field__item-wrapper"><a href="/taxonomy/term/18" hreflang="en">rocket.chat</a></span> </div> <div class="field__item field__item--mongodb"> <span class="field__item-wrapper"><a href="/taxonomy/term/14" hreflang="en">mongodb</a></span> </div> </div> </div> <span class="field field--name-uid field--type-entity-reference field--label-hidden"><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">dave</span></span> <span class="field field--name-created field--type-created field--label-hidden">Thu 24/11/2016 - 10:56</span> <div class="clearfix text-formatted field field-node--body field-name-body field-type-text-with-summary field-label-hidden"> <div class="field__items"> <div class="field__item"><p>(Update 2017-05-24: see an <a href="/docker-compose-better-way-deploy-rocketchat-wekan-and-mongodb">easier way to run</a> Wekan, Rocketchat, and MongoDB) You may not have all of these installed, but if you're running <a href="/node/4">Wekan</a> or Rocket.Chat based on our instructions, you'll also have <a href="/node/3">MongoDB</a>, and the need to keep them all up-to-date to benefit from their rapid development processes (quick bug fixes and new and improved features every few days!).</p> <p>I'd recommend doing an upgrade monthly or more rapidly if you hear about security issues, or fixes to any bugs which might be affecting you.</p> <h2>Upgrading Step By Step</h2> <p>1. find the 3 IDs of the mquandalle/wekan, rocketchat/rocket.chat, and mongo containers you're running via</p> <p><code>docker ps</code></p> <p>The output might look something like this:</p> <p><code>5c35737f4de2      mongo                         "/entrypoint.sh mongo"   45 hours ago        Up 45 hours         27017/tcp                                        oeru-mongo<br /> 7e8ef524ba0a        mquandalle/wekan              "/bin/sh -c 'bash $ME"   45 hours ago        Up 45 hours         127.0.0.1:5555-&gt;80/tcp                           plan<br /> b370ad72f358        rocketchat/rocket.chat        "node main.js"           45 hours ago        Up 45 hours         127.0.0.1:7996-&gt;3000/tcp                         chat</code></p> <p>in this case, the relevant IDs are 5c35737f4de2, 7e8ef524ba0a, and b370ad72f358</p> <p>2. Stop the containers:</p> <p>docker stop 5c35737f4de2 7e8ef524ba0a b370ad72f358</p> <p>3. Remove the old containers (not the images):</p> <p><code>docker rm 5c35737f4de2 7e8ef524ba0a b370ad72f358</code></p> <p>4. Update the images to the latest versions for each.</p> <p><code>docker pull mongo<br /> docker pull mquandalle/wekan<br /> docker pull rocketchat/rocket.chat</code></p> <p>5. restart updated MongoDB (create new container) using the same "docker run" command you used initially.</p> <p>6. similarly restart your updated Rocket.Chat and Wekan containers, using exactly the same "docker run" command you used initially.</p> <p> </p> </div> </div> </div> <section class="field field-node--field-blog-comments field-name-field-blog-comments field-type-comment field-label-above comment-wrapper"> <a name="comments"></a> <div class="comment-form-wrapper"> <h2 class="comment-form__title">Add new comment</h2> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=6&amp;2=field_blog_comments&amp;3=comment" token="dpYbd2vVnCJCXcQ8O44h111--q6kOHp4vaZqvGM8kLU"></drupal-render-placeholder> </div> </section> Wed, 23 Nov 2016 21:56:41 +0000 dave 6 at http://tech.oeru.org Installing Wekan with Docker on Ubuntu Linux 14.04 http://tech.oeru.org/installing-wekan-docker-ubuntu-linux-1404 <span class="field field--name-title field--type-string field--label-hidden">Installing Wekan with Docker on Ubuntu Linux 14.04</span> <div class="field field-node--field-blog-tags field-name-field-blog-tags field-type-entity-reference field-label-above"> <h3 class="field__label">Blog tags</h3> <div class="field__items"> <div class="field__item field__item--install"> <span class="field__item-wrapper"><a href="/taxonomy/term/11" hreflang="en">install</a></span> </div> <div class="field__item field__item--ubuntu-linux"> <span class="field__item-wrapper"><a href="/taxonomy/term/12" hreflang="en">ubuntu linux</a></span> </div> <div class="field__item field__item--_404"> <span class="field__item-wrapper"><a href="/taxonomy/term/13" hreflang="en">14.04</a></span> </div> <div class="field__item field__item--mongodb"> <span class="field__item-wrapper"><a href="/taxonomy/term/14" hreflang="en">mongodb</a></span> </div> <div class="field__item field__item--wekan"> <span class="field__item-wrapper"><a href="/taxonomy/term/15" hreflang="en">wekan</a></span> </div> <div class="field__item field__item--docker"> <span class="field__item-wrapper"><a href="/taxonomy/term/16" hreflang="en">docker</a></span> </div> <div class="field__item field__item--lets-encrypt"> <span class="field__item-wrapper"><a href="/taxonomy/term/17" hreflang="en">let&#039;s encrypt</a></span> </div> </div> </div> <span class="field field--name-uid field--type-entity-reference field--label-hidden"><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">dave</span></span> <span class="field field--name-created field--type-created field--label-hidden">Thu 27/10/2016 - 14:12</span> <div class="field field-node--field-image field-name-field-image field-type-image field-label-hidden has-multiple"> <figure class="field-type-image__figure image-count-1"> <div class="field-type-image__item"> <a href="http://tech.oeru.org/sites/default/files/styles/max_1300x1300/public/2016-11/Wekan.png?itok=DTYp9TJz" title="OERu&#039;s Wekan instance" data-colorbox-gallery="gallery-field_image-LjTTYF0ikCw" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;OERu&#039;s Wekan instance&quot;}"><img src="/sites/default/files/styles/medium/public/2016-11/Wekan.png?itok=a6RIb_sO" width="220" height="112" alt="OERu&#039;s Wekan instance" typeof="foaf:Image" class="image-style-medium" /> </a> </div> </figure> <figure class="field-type-image__figure image-count-2"> <div class="field-type-image__item"> <a href="http://tech.oeru.org/sites/default/files/styles/max_1300x1300/public/2016-11/Wekan2.png?itok=CFI0It_1" title="Editing a task, setting participants and priorites" data-colorbox-gallery="gallery-field_image-LjTTYF0ikCw" class="colorbox" data-cbox-img-attrs="{&quot;alt&quot;:&quot;Editing a task, setting participants and priorites&quot;}"><img src="/sites/default/files/styles/medium/public/2016-11/Wekan2.png?itok=IfrWQAmQ" width="220" height="113" alt="Editing a task, setting participants and priorites" typeof="foaf:Image" class="image-style-medium" /> </a> </div> </figure> </div> <div class="clearfix text-formatted field field-node--body field-name-body field-type-text-with-summary field-label-hidden"> <div class="field__items"> <div class="field__item"><p><a href="https://wekan.org">Wekan</a> is an excellent, easy-to-use "kanban board" project management support tool, suitable for all manner of projects. For those who have used the highly marketed Trello kanban service, Wekan is functionally similar open source alternative that organisations can host and control for themselves. They can also enhance it in whatever ways they are moved to do so. We encourage our partner institutions to consider this path as a way of reducing costs as well as increasing freedom and privacy. To make migrating a win-win, we have also found that Wekan is able to import entire Trello boards, preserving your data. (Update: 2017-05-24 we've just published an <a href="/docker-compose-better-way-deploy-rocketchat-wekan-and-mongodb">easier way to run</a> Wekan and MongoDB)</p> <p>The OERu <a href="https://plan.oeru.org">provides a Wekan instance</a> that has proven very popular with our Open Education Resource designers and collaborators.</p> <p>Wekan instances store their data in another open source tool, a key-value storage engine called <a href="https://www.mongodb.com/">MongoDB</a> - these instructions assume that you have already got a running MongoDB installed, and to facilitate that, we've provided this <a href="/node/3">handy MongoDB install guide</a> as a companion.</p> <p>This guide will cover both configuring and launching a Docker container with a working instance of Wekan. It will, in turn, be linked to another Docker container running MongoDB, and both will be capable of sending email via external authenticating email server. External user access to Wekan is provided by the Nginx web server (as a forward proxy). User interaction with Wekan is (and should always be) encrypted via recognised SSL certificates using the brilliant (and gratis) <a href="http://letsencrypt.org">Let's Encrypt</a> service.</p> <p>This instruction set assumes that you have command-line access (via SSH, in most cases) to your server, running Ubuntu Linux 14.04, probably a Virtual Machine hosted in a data centre somewhere - a very inexpensive way to do this is, for example, via <a href="https://digitalocean.com">DigitalOcean</a> (for the record, we have no relationship with DigitalOcean, I simply have substantial experience with their services), but there are many many options worldwide. These instructions will need to be modified slightly for other versions of Linux (e.g. Debian 8 or Ubuntu 16.04 or CentOS or others), but should be mostly valid. We'd be grateful to hear about anyone else's experiences in the comments below!</p> <h2>Installing Docker</h2> <p>See our instructions in the <a href="/node/3">MongoDB blog post</a>.</p> <h2>Installing Wekan</h2> <p>First, we would normally create a wekan user:</p> <p><code>sudo adduser wekan</code></p> <p>and then create a directory in that user's space to store Wekan-related data (so it survives updates to the Docker image):</p> <p>sudo -u wekan mkdir /home/wekan/public</p> <p>Assuming you've got Docker installed properly, to install the official Wekan Docker image, you can just run:</p> <p><code>docker pull mquandalle/wekan</code></p> <p>Then you can launch it by running this by first launching your MongoDB container, and then running this (assumes you've named your MongoDB instance "mongodb" as per our <a href="/node/3">instructions</a>):</p> <p><code>docker run -d --name wekan -p 127.0.0.1:5555:80 \<br />          -h [your_Wekan_domain] -e "VIRTUAL_HOST=[your_Wekan_domain]" \<br />          -v /home/wekan/public:/built_app/programs/web.browser/app \<br />          -e "MAIL_URL=[outgoing_mail_server]" \<br />          --link mongodb:mongo -e "MONGO_URL=mongodb://mongo/plan" \<br />          -e "ROOT_URL=http://[your_Wekan_domain]" \<br />          --restart unless-stopped mquandalle/wekan</code></p> <p><strong>Note: Please copy and paste the exact text of your "docker run" command into a reference file (I usually have a "README.oeru" reference file in the home directory of each Docker-based app I run)  as you will want to refer to it when doing upgrades!</strong></p> <p>You'll need to make sure you set appropriate values for [your_Wekan_domain] and details for an SMTP (outgoing mail) server, because Wekan needs to send emails. You can use either a local mail server on the Docker host, in which case you'd put the local IP address of your server, as it would be seen by the Docker container, so 172.17.42.1 is the default IP of a Docker host. You could also use an authenticating SMTP server, and specify the details like this: <code>smtp://[username]:[password]@[IP-or-domainname]:[port, usually 25, 465, or 587].</code> Here's a what a made-up example might look like:</p> <p><code>-e "MAIL_URL=smtp://smtpmail:blahdiblah88@mail.oeru.org:25"</code></p> <p>Note, the <code>--restart unless-stopped</code> will ensure that this container is restarted on a reboot unless it's explicitly stopped, like via a <code>docker stop wekan</code>, like you might to update the Docker container.</p> <h2>Setting up Nginx as a proxy server</h2> <p>Having set up the Docker container, which is listening on port 5555 on the Docker container's <em>host</em>, you'll need to set up a reverse proxy on that host to make the site visible to the broader internet on your public IP address (you'll want to make sure the domain you specified above points to that IP address or is a CNAME to a domain that does).</p> <p>Once you've got that, you can proceed. In /etc/nginx/sites-available, I create a file called plan (as we use plan.oeru.org as our domain). Note, I use "vim" as my text editor. If you don't know it, perhaps use "nano" instead. It's much less powerful, but easier to use...</p> <p><code>sudo vim /etc/nginx/sites-available/plan</code></p> <p>Here're the contents - you'll want to change the domain name to suit your own choices. Similarly the names of SSL files and logs. The following file has a chunk in the middle commented out with "#s". More on that below:</p> <p><blockcode><code># from https://github.com/wekan/wekan/wiki/Install-Wekan-Docker-in-production<br /> upstream websocket {<br />         server 127.0.0.1:5555;<br /> }</code></blockcode></p> <p><blockcode><code>map $http_upgrade $connection_upgrade {<br />         default upgrade;<br />         '' close;<br /> }</code></blockcode></p> <p><blockcode><code>server {<br />         listen  80; # this is one of our external IPs on the server.<br />         #listen   [::]:80 default ipv6only=on; ## listen for ipv6<br /><br />         root /usr/share/nginx/www;<br />         index index.html index.htm;<br /><br />         server_name plan.oeru.org;<br /><br />         access_log /var/log/nginx/plan.oeru.org_access.log;<br />         error_log /var/log/nginx/plan.oeru.org_error.log;<br /><br />         location /.well-known {<br />                 root /var/www/html;<br />                 default_type text/plain;<br />         }<br /><br /> #        location / {<br /> #                return 301 https://plan.oeru.org$request_uri;<br /> #        }       <br /> #}<br /> #<br /> #server {<br /> #        listen 443 ssl;<br /> #        ssl on;<br /> #        ssl_certificate /etc/letsencrypt/live/plan.oeru.org/fullchain.pem;<br /> #        ssl_certificate_key /etc/letsencrypt/live/plan.oeru.org/privkey.pem;</code><br /><code># </code><code>       ssl_protocols TLSv1 TLSv1.1 TLSv1.2;<br /> #        ssl_dhparam /etc/ssl/certs/dhparam.pem;<br /> #        keepalive_timeout 20s;<br /> #<br /> #        access_log /var/log/nginx/plan.oeru.org_access.log;<br /> #        error_log /var/log/nginx/plan.oeru.org_error.log;</code></blockcode><blockcode><br /><code>#        root /var/www/html;<br /> #        index index.html index.htm;</code></blockcode><blockcode><br /><code>#        server_name plan.oeru.org;<br /> #<br /> #        location /.well-known {<br /> #                root /var/www/html;<br /> #                default_type text/plain;<br /> #        }</code></blockcode></p> <p><blockcode><code>        location / {<br />                 proxy_read_timeout      300;<br />                 proxy_connect_timeout   300;<br />                 proxy_redirect          off;<br />                 proxy_set_header    X-Real-IP           $remote_addr;<br />                 proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for;<br />                 proxy_set_header    X-Forwarded-Proto   $scheme;<br />                 proxy_pass      http://127.0.0.1:5555;<br />                 proxy_set_header Host           $host;<br />         }</code></blockcode></p> <p><blockcode><code>        location ~ websocket$ {<br />                 proxy_pass http://websocket;<br />                 proxy_http_version 1.1;<br />                 proxy_set_header Upgrade $http_upgrade;<br />                 proxy_set_header Connection $connection_upgrade;<br />         }<br /> }</code></blockcode></p> <p>When you've set up the file, you can enable it:</p> <p><code>sudo ln -sf /etc/nginx/sites-available/plan /etc/nginx/sites-enabled</code></p> <p>Test the file to ensure there aren't any syntax errors before reloading nginx:</p> <p><code>sudo nginx -t</code></p> <p>If this shows an error, you'll need to fix the file. If all's well, reload nginx to include the new configuration:</p> <p><code>sudo service nginx reload</code></p> <p>You should now be able to point your browser at your domain name, and you should get your Wekan site via the HTTP (not encrypted) protocol.</p> <p>A word to the wise - if it doesn't work, check your firewall settings!</p> <p>In the next step, we'll sort out your SSL certificate from <a href="https://letsencrypt.org/" title="Let's Encrypt - libre and gratis SSL certificates">Let's Encrypt</a>.</p> <h2>Protecting your users</h2> <p>Have a look at our <a href="/protecting-your-users-lets-encrypt-ssl-certs">Let's Encrypt howto</a>.</p> <h2>Upgrading Wekan</h2> <p>You should periodically upgrade Wekan, say every couple months, to benefit from improved features... The upgrade process usually means a few minutes of down time for the site, so pick a time when it isn't likely to be heavily used... You'll normally want to upgrade any dependent Docker containers at the same time (like <a href="/node/3">your MongoDB</a> and <a href="/node/5">Rocket.Chat</a>) so we have a <a href="/node/6">dedicated Upgrade article</a> for that.</p> <p> </p> </div> </div> </div> <section class="field field-node--field-blog-comments field-name-field-blog-comments field-type-comment field-label-above comment-wrapper"> <a name="comments"></a> <div class="comment-form-wrapper"> <h2 class="comment-form__title">Add new comment</h2> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=4&amp;2=field_blog_comments&amp;3=comment" token="uc1Lb6EN7BABm6o1LG_bvkNtKR1zMSzFvkh2eSYvs90"></drupal-render-placeholder> </div> </section> Thu, 27 Oct 2016 01:12:48 +0000 dave 4 at http://tech.oeru.org Installing MongoDB with Docker on Ubuntu Linux 14.04 http://tech.oeru.org/installing-mongodb-docker-ubuntu-linux-1404 <span class="field field--name-title field--type-string field--label-hidden">Installing MongoDB with Docker on Ubuntu Linux 14.04</span> <div class="field field-node--field-blog-tags field-name-field-blog-tags field-type-entity-reference field-label-above"> <h3 class="field__label">Blog tags</h3> <div class="field__items"> <div class="field__item field__item--install"> <span class="field__item-wrapper"><a href="/taxonomy/term/11" hreflang="en">install</a></span> </div> <div class="field__item field__item--ubuntu-linux"> <span class="field__item-wrapper"><a href="/taxonomy/term/12" hreflang="en">ubuntu linux</a></span> </div> <div class="field__item field__item--_404"> <span class="field__item-wrapper"><a href="/taxonomy/term/13" hreflang="en">14.04</a></span> </div> <div class="field__item field__item--docker"> <span class="field__item-wrapper"><a href="/taxonomy/term/16" hreflang="en">docker</a></span> </div> <div class="field__item field__item--mongodb"> <span class="field__item-wrapper"><a href="/taxonomy/term/14" hreflang="en">mongodb</a></span> </div> </div> </div> <span class="field field--name-uid field--type-entity-reference field--label-hidden"><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">dave</span></span> <span class="field field--name-created field--type-created field--label-hidden">Thu 27/10/2016 - 14:06</span> <div class="clearfix text-formatted field field-node--body field-name-body field-type-text-with-summary field-label-hidden"> <div class="field__items"> <div class="field__item"><p>This post describes installing <a href="https://www.mongodb.com/">MongoDB</a> (and backing it up and restoring from backup) in a Docker container on an Ubuntu Linux 14.04 virtual machine. (Update: 2017-05-24 we've just published an <a href="/docker-compose-better-way-deploy-rocketchat-wekan-and-mongodb">easier way to run</a> MongoDB and Rocket.Chat and Wekan which depend on it)</p> <p>The motivation for installing MongoDB is that it is the key-value based data container engine used by two compelling web technologies, <a href="https://rocket.chat">Rocket.Chat</a> and <a href="http://wekan.org">Wekan</a>, that the OER Foundation has deployed for the OERu as <a href="https://chat.oeru.org">https://chat.oeru.org</a> and <a href="https://plan.oeru.org">https://plan.oeru.org.</a></p> <h2>Installing Docker</h2> <p>Rather than trying to write a comprehensive guide to installing Docker, I'll instead recommend that you have a look at <a href="https://docs.docker.com/engine/installation/linux/ubuntulinux/">this one</a> provided by the Docker community. It also covers installing on other versions of Ubuntu which might be helpful for some. Once you've got a working Docker container install going on your server, you can continue to the next step...</p> <p>Note: the idea with Docker is that <em>nothing important is stored <strong>in</strong> the container itself. </em>That means that you can blow it away at any time without losing any important data. You can do that to quickly upgrade it, or to change its properties. You simply re-launch the container making sure it points properly to the persistent stuff you want it to use.</p> <h2>Installing MongoDB</h2> <p>Once you've got Docker working on your virtual machine, getting the MongoDB docker container (where it's preinstalled and ready to go) is simple:</p> <p><code>docker pull mongo</code></p> <p>This will install the latest version of the MongoDB container from docker.io.</p> <h2>Launching MongoDB</h2> <p>I recommend setting up a per-container user to help keep things well segmented. You can create a "mongo" user:</p> <p><code>sudo adduser mongo</code></p> <p>This creates a directory /home/mongo and that's where we'll get the Mongo container to store the data we want to keep persistent.</p> <p>We can create "data" and "backups" directories in which it should store its data and save its backups respectively:</p> <p><code>sudo mkdir /home/mongo/data<br /> sudo mkdir /home/mongo/backup</code>s</p> <p>Then you can launch the container:</p> <p><code>docker run --name mongodb --restart unless-stopped \<br /> -v /home/mongo/data:/data/db -v /home/mongo/backups:/backups -d mongo --smallfiles</code></p> <p>You should be able to check it's running by typing</p> <p><code>docker ps</code></p> <p>and looking for the reference to "mongodb" (that's the "name" we've assigned to this instance of the "mongo" container).</p> <p>You can even log into the container and poke around the filesystem if you like, using the container's "id":</p> <p><code>ID=`docker ps | grep mongo | cut --characters=1-12`<br /> docker exec -it $ID bash</code></p> <p>which will drop you at the command line in the container. You can exit with "exit" or CTRL-D.</p> <p>If things go wrong, you can run this:</p> <p><code>ID=`docker ps | grep mongo | cut --characters=1-12`<br /> docker inspect $ID | grep log </code></p> <p>Which will give you a result like this (your filenames will be different):</p> <p><code>"LogPath": "/var/lib/docker/containers/539072b8fb976b5048bd13e90074ea4b43166b81c3d69d1720b4746785f7d917/539072b8fb976b5048bd13e90074ea4b43166b81c3d69d1720b4746785f7d917-json.log",</code></p> <p>In which case, you can look at the log file to see what's gone wrong:</p> <p><code>less +G /var/lib/docker/containers/539072b8fb976b5048bd13e90074ea4b43166b81c3d69d1720b4746785f7d917/539072b8fb976b5048bd13e90074ea4b43166b81c3d69d1720b4746785f7d917-json.log</code><br /><br /> You can exit less by hitting "q" or CTRL-C.</p> <h2>Backing up MongoDB</h2> <p>Backing up MongoDB requires a somewhat circuitous process. Here's what I do (after quite a bit of head scratching and web searching - Note: these backups are not directly related to the "backups" directory created above. More on that in the script below.):</p> <p>On the host VM, I create a directory for backups - I use /home/backup</p> <p><code>sudo mkdir /home/backup</code></p> <p>and I create a bin directory in /home/mongo</p> <p><code>sudo mkdir /home/mongo/bin</code></p> <p>and I create a script file with my preferred editor (I use vim, you could use nano instead if vim scares you):</p> <p><code>sudo vim /home/mongo/bin/mongo_backup.sh</code></p> <p>In it, I put the following:</p> <p><blockcode>#!/bin/bash<br /> #<br /> # backup all the mongo DBs hosted on this server, in a docker container<br /> #<br /> # useful reference: <a href="https://docs.mongodb.org/manual/core/backups/">https://docs.mongodb.org/manual/core/backups/</a><br /> #<br /> # in event of a disaster, this could be useful:<br /> # <a href="https://docs.mongodb.org/manual/tutorial/recover-data-following-unexpected-shutdown/">https://docs.mongodb.org/manual/tutorial/recover-data-following-unexpec…</a><br /> #<br /> # script copyright <a href="mailto:dave@davelane.nz">dave@davelane.nz</a> - available under terms of GPL v2 or later,<br /> # see <a href="https://www.gnu.org/licenses/old-licenses/gpl-2.0.html">https://www.gnu.org/licenses/old-licenses/gpl-2.0.html</a><br /> #<br /> # Note: I've commented out some "echo" statements below that might be useful if you're<br /> # debugging this script...<br /> DATE=`date '+%Y%m%d-%H%M%S'`<br /> # this is the local backup directory - gets cleared out after every backup...<br /> DIR=/home/mongo/backups<br /> # the archive dir, with a past archive as well.<br /> ARCHDIR=/home/backup/mongodb<br /> PREV=$ARCHDIR/previous<br /> LATEST=$ARCHDIR/latest<br /> #<br /> # this is the backup directory on the Docker host<br /> DDIR=/backups</blockcode><blockcode><br /> # 1. get mongo container (this assumes only one running on the machine, with "mongo" in is name!)<br /> ID=`docker ps | grep "mongo" | cut -c 1-12`<br /> ##echo "working with ID = $ID"<br /> #<br /> # 2. back up databases<br /> # 2.1 backup to the local $DDIR on the Docker host<br /> MSG=`docker exec -i $ID mongodump --quiet --out $DDIR`<br /> ##echo $MSG<br /> # go into local backup dir<br /> CWD=`pwd`<br /> cd $DIR<br /> # 2.2. get list of backup databases<br /> DBS=`find $DIR -mindepth 1 -maxdepth 1 -type d -exec basename {} \;`<br /> for DB in $DBS<br /> do<br />     ARCH="${DB}.tgz"<br />     if [ -f $PREV/$ARCH ] ; then<br />         ##echo "explicitly jettison old backup of $PREV/$ARCH"<br />         rm $PREV/$ARCH<br />     else<br />         echo "***no previous version of $ARCH in $PREV on $DATE"<br />     fi<br />     if [ -f $LATEST/$ARCH ] ; then<br />         ##echo "move previous $LATEST/$ARCH into $PREV (overwritting old)"<br />     mv $LATEST/$ARCH $PREV/$ARCH<br />     else<br />         echo "***no latest backup $ARCH in $LATEST on $DATE"<br />     fi<br />     ##echo "archiving contents of $DB into $LATEST/$ARCH"<br />     tar cvfz $LATEST/$ARCH $DB<br />     #echo "removing $DB *.json and *.bson and the dir"<br />     rm -f $DB/*.bson $DB/*.json<br />     rmdir $DB<br /> done<br /> # return to where we started<br /> cd $CWD<br /> # exit with a happy 0, which shows we were successful!<br /> exit 0</blockcode></p> <p>Save that file, and then run this to fix the ownership of the file and make the script executable:</p> <p><code>sudo chown -R mongo:mongo /home/mongo/*<br /> sudo chmod a+x /home/mongo/bin/mongo_backup.sh</code></p> <p>You can try running the script to make sure it creates a backup of your mongo database (such as it is! There might not be much there yet, if you haven't got any apps installed that use MongoDB - you can wait until after that before testing. If you run it twice, you should see both a "latest" and "previous" backup directory containing suitably dated directories - this provides extra data security):</p> <p><code>sudo /home/mongo/bin/mongo_backup.sh</code></p> <p>Once you're happy that's working, set up a cron job to make it run daily... Again, edit a file with your preferred text editor:</p> <p><code>sudo vim /etc/cron.d/mongodb-backup</code></p> <p>and fill it with the following (replacing [an email that will get to you!] appropriately):<blockcode></blockcode></p> <p><blockcode>#<br /> # cron.d/mongodb-backup -- backups up the Mongo DBs running in a docker container<br /> #<br /> #<br /> MAILTO=[and email that will get to you!]</blockcode><br /><blockcode># run daily at 12:17pm as the root user<br /> 17 12 * * *  root  /home/mongo/bin/mongo_backup.sh</blockcode></p> <h2>Recovering a MongoDB database</h2> <p>In the event that you ever have to recover or transfer a MongoDB database, aka container, you can do so like this (sort of the reverse of the backup script):</p> <p>work out the name of the container you want to restore and copy the backup of the database directory (e.g. rocketchat or wekan) into /home/mongo/backups - to be clear: for MongoDB, a "database" is represented as a directory of files holding all the relevant data.</p> <p>Then log into Docker instance:</p> <p><code>ID=`docker ps | grep mongo | cut --characters=1-12`<br /> docker exec -it $ID bash</code></p> <p>and on <em>the Docker container's command line</em> run this, replacing the [names] appropriately below. Note, you might want to change the database name from the backup's original to a new name, so they don't need to be the same (but they can be):</p> <p><code>mongorestore --db [recovered DB Name] --drop /backups/[backed up DB Name]/</code></p> <h2>Upgrading MongoDB</h2> <p>Note, if you have other apps in other containers that depend on MongoDB, it's usually most convenient to upgrade them all at the same time. Look at our instructions for, for example, <a href="/node/4">running Wekan</a>, to see how to upgrade MongoDB.</p> </div> </div> </div> <section class="field field-node--field-blog-comments field-name-field-blog-comments field-type-comment field-label-above comment-wrapper"> <a name="comments"></a> <div class="comment-form-wrapper"> <h2 class="comment-form__title">Add new comment</h2> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=3&amp;2=field_blog_comments&amp;3=comment" token="sakYYvZOls5kfGBdE8P9OqUsJ8weSKuhrRjDRw2dR04"></drupal-render-placeholder> </div> </section> Thu, 27 Oct 2016 01:06:55 +0000 dave 3 at http://tech.oeru.org