The definitive guide to using Docker as a local development environment (part 2)

This article assumes you have read part 1. If you haven’t yet, make sure to read it first.

In this part we will focus on the following points :

  1. How to setup a database in your development environment?
  2. How to open more than 1 terminal instance?

Make Docker your home

Makes you feel like home

1. How to setup a database in your development environment?

Having a sandboxed web server is fine, but most of the time it relies on a database for persistence. Let’s see how to create a MongoDB container.

Doing things manually

Now you know how things go: google install mongodb ubuntu and add the steps to the Dockerfile :

  • docker-compose.yml:
db:
  build: .
  volumes:
    - .:/db
  ports:
    - '27017:27017'
  • Dockerfile:
FROM ubuntu:latest

RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv EA312927
RUN echo "deb http://repo.mongodb.org/apt/ubuntu "$(lsb_release -sc)"/mongodb-org/3.2 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-3.2.list

RUN apt-get update
RUN apt-get install -y mongodb-org

# etc.

Then run docker-compose build and done, right? Wait…

The better way: use the directory

While the above would eventually work, this is definitely not the docker-way of doing this. The great thing about Docker is that besides offering a lightweight virtualbox, it also provides access to a huge directory of setup scripts: the DockerHub.

Pulling the mongo docker

DockerHub hosts official and unofficial docker images

There you will find all the Docker images that the community accepted to share. It works like Github: if your image is meant to be public (= open source) then hosting is free, if it needs to be private, you will be charged a fee. In our case, we simply need to download an official image so no registration is required.

No wonder that you will find several that setup MongoDB. There is even an official one.

So you can enter the MongoDB docker using the following docker-compose.yml:

image: mongo
ports:
  - '27017:27017'

And type docker-enter. But is it really what you want to do?

Connect Mongo to your Node Docker

Most of the time, what you need is the database server to remain unchanged, and simply let your server connect to it, right?

This is where docker-compose really shines. Go back to your app docker-compose.yml and add the following:

app:
  build: .
  script: node index.js # Added
  volumes:
    - .:/app
  ports:
    - '3000:3000'
  links:
    - db
db:
  image: mongo

You can now run docker-enter and see what is going on:

Pulling the mongo docker

The offical Mongo docker gets downloaded

The Mongo Docker gets pulled because it is not yet present locally. Once it is done, yai, you are inside a virtual machine with NodeJS installed on it and a MongoDB connected to it.

This is great! But before we continue, can’t you think of some improvement we could make on the app Dockerfile?

Yes! You are correct: instead of installing manually NodeJS, why not using the official Node Docker, just like we did for the Mongo one?

Let’s edit our Dockerfile:

FROM node:latest # instead of ubuntu:latest + installing NodeJS

WORKDIR /app
ADD package.json .
RUN npm install

Now you’ll have to rerun docker-compose build (otherwise the previous version will be used) and then docker-enter. You are getting good! :)

Accessing Mongo from you app

I have told you Mongo is connected to your app, but until now you mostly had to trust me about this. How do I check that?

Try typing printenv:

$ printenv
DB_1_PORT_27017_TCP_ADDR=172.17.0.2
NODE_VERSION=8.0.0
DB_ENV_MONGO_PACKAGE=mongodb-org
HOSTNAME=9befb8f7bfe7
DB_NAME=/test_web_run_1/db
DB_ENV_GPG_KEYS=0C49F3730359A14518585931BC711F9BA15703C6
DB_PORT_27017_TCP=tcp://172.17.0.2:27017
TERM=xterm
DB_PORT=tcp://172.17.0.2:27017
TEST_DB_1_ENV_GOSU_VERSION=1.7
TEST_DB_1_PORT_27017_TCP_PROTO=tcp
DB_ENV_MONGO_VERSION=3.4.4
DB_ENV_GOSU_VERSION=1.7
NPM_CONFIG_LOGLEVEL=info
DB_PORT_27017_TCP_PORT=27017
DB_1_PORT_27017_TCP_PROTO=tcp
DB_1_ENV_MONGO_PACKAGE=mongodb-org
TEST_DB_1_ENV_GPG_KEYS=0C49F3730359A14518585931BC711F9BA15703C6
TEST_DB_1_PORT_27017_TCP=tcp://172.17.0.2:27017
DB_1_PORT=tcp://172.17.0.2:27017
TEST_DB_1_ENV_MONGO_MAJOR=3.4
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
DB_1_ENV_MONGO_VERSION=3.4.4
DB_1_PORT_27017_TCP_PORT=27017
PWD=/app
DB_ENV_MONGO_MAJOR=3.4
DB_PORT_27017_TCP_PROTO=tcp
TEST_DB_1_PORT_27017_TCP_PORT=27017
SHLVL=1
HOME=/root
TEST_DB_1_ENV_MONGO_PACKAGE=mongodb-org
YARN_VERSION=0.24.6
TEST_DB_1_PORT_27017_TCP_ADDR=172.17.0.2
DB_PORT_27017_TCP_ADDR=172.17.0.2
TEST_DB_1_ENV_MONGO_VERSION=3.4.4
DB_1_ENV_MONGO_MAJOR=3.4
DB_1_NAME=/test_web_run_1/db_1
DB_1_ENV_GPG_KEYS=0C49F3730359A14518585931BC711F9BA15703C6
DB_1_PORT_27017_TCP=tcp://172.17.0.2:27017
TEST_DB_1_NAME=/test_web_run_1/test_db_1
TEST_DB_1_PORT=tcp://172.17.0.2:27017
DB_1_ENV_GOSU_VERSION=1.7
_=/usr/bin/printenv

Ouch, that’s a lot of things. Don’t worry we don’t need it all. What interests us is the Mongo IP and port, respectively 172.17.0.2 and 27017.

How to bring this into NodeJS? You already know how to do this I’m sure:

$ npm install mongodb --save

Inspired from the MongoDB doc, edit your index.js this way:

const express = require('express')
const app = express()

const MongoClient = require('mongodb').MongoClient

// Connection URL
const url = `mongodb://${process.env.DB_PORT_27017_TCP_ADDR}:${process.env.DB_PORT_27017_TCP_PORT}/app`

MongoClient.connect(url, function(err, db) {
  console.log("Connected successfully to server")
})

app.get('/', function (req, res) {
  res.send('Docker is great!')
})

app.listen(3000, function () {
  console.log('Example app listening on port 3000!')
})

And try it:

$ node index.js
Example app listening on port 3000!
Connected successfully to server

Persist the data

Docker containers are meant to be disposable. Wipe and rebuild’em as often as you want! Just like softwares: when you open your browser, your photo editor, the calculator, or any software, it starts and then live until being closed. The only things that remains after this lifecycle are files.

Well in the application world, the server is the software, and the database is the file: you want to make it persist so that you have to signup in your local environment each time you want to work with it.

There is a solution to achieve this: your working directory is what remains between docker executions, your source code. Why not also set the database content there?

Let’s configure it for the above Mongo DB:

$ mkdir data # Or whatever location that makes sense to you

Add this folder to your .gitignore otherwise you will commit all your database content to your source code

data

And in docker-compose.yml, add the volumes entry to the db block that matches the location of the MongoDB content:

app:
  build: .
  script: node index.js # Added
  volumes:
    - .:/app
  ports:
    - '3000:3000'
  links:
    - db
db:
  image: mongo
  volumes:
    - ./data:/data/db

Unfortunately there is not 1 solution for all databases since each database system stores the data at different location. To find the solution for your DB simply google where is data stored in <DB Name> or how to make <DB Name> data persist docker and you will find it.

Examples:

2. How to open more than 1 terminal instance?

So I got a server with its persistant database running. Fine, but that’s not all I need when I work locally. Most of the time, I also need to run scripts, to watch for files, open a console etc. So I need another terminal. Without stopping the server, let’s open a new terminal tab and go for:

$ docker-enter
ERROR: Cannot start service app: driver failed programming external connectivity on endpoint project_app_run_17 (4d8195c447cc4298c07f76488b9310b6d29430ecdf0f866befe2fb6816b3aa11): Bind for 0.0.0.0:3000 failed: port is already allocated

Ouch doesn’t work. Why? Well message is pretty clear: the port 3000 is already allocated to your other docker instance. That’s when our second alias comes into play. Try typing instead:

$ docker-enter-again # Same as `docker-enter`, but without the `--service-ports` flag.

In there, you will be able to configure every thing you need: your webpack build, run the beautiful rails console, resize your images etc.

Pro-tip: You need to be aware that the context of this new container does not share context with the server one. They both start in the state of the last build. It means if you run apt-get install vim in an instance, it won’t be available in another one. As mentionned above, the only thing that is shared is the content of the shared folder: your app working directory.

Final word (for now)

From this, you should have several keys to being able to work locally with docker. If you are having some trouble, please comment this article and I will definitely help you around. If some regular questions arise, I shall edit my article or write a third part. Docker is a huge project, there are tons of possible ways to use it, so be aware that this content was simply introductory and very opinionated from my usage.

Some docker experts might even consider this is non-sense, as this was not what Docker was created for. And so what? I have been surprised of the few litterature available on the topic and this is what took me to write this, after some time practicing by myself.

Finally, let me suggest you a last alias:

alias docker-clean="docker ps -a | grep 'Exited\|Created' | cut -d ' ' -f 1 | xargs docker rm"

Why?

Because despite the --rm option that is supposed to remove the instance after it has been run, sometimes you won’t exit things correctly, and some instances happen to remain ghosts on your laptop, docker-clean will remove those.

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                     PORTS                                         NAMES
cbcd659cf8a8        webiteasy_web       "/bin/bash"              12 minutes ago      Created                                                                  webiteasy_web_run_17
e560889ce3b9        webiteasy_web       "/bin/bash"              25 minutes ago      Up 25 minutes              4000/tcp, 0.0.0.0:3000->3000/tcp, 35729/tcp   webiteasy_web_run_16
b6c16dabdc26        copass_web          "/bin/bash"              2 hours ago         Created                                                                  copass_web_run_1
f4fb9a6fda29        mdillon/postgis     "docker-entrypoint..."   2 hours ago         Up 2 hours                 0.0.0.0:32768->5432/tcp                       copass_db_1
acd1631890fe        webiteasy_web       "/bin/bash"              18 hours ago        Created                                                                  webiteasy_web_run_15
0fa3afb6d91d        webiteasy_web       "/bin/bash"              4 days ago          Up 4 days                  4000/tcp, 35729/tcp                           webiteasy_web_run_14
9ea333c98e6f        webiteasy_web       "node local.js"          4 days ago          Exited (137) 2 hours ago                                                 webiteasy_web_1
fb23ecb83d3b        webiteasy_web       "/bin/bash"              4 days ago          Exited (0) 4 days ago                                                    webiteasy_web_run_13
887fbbad4c87        webiteasy_web       "/bin/bash"              4 days ago          Exited (137) 4 days ago                                                  webiteasy_web_run_12
5fbe10194c42        webiteasy_web       "/bin/bash"              4 days ago          Exited (0) 4 days ago                                                    webiteasy_web_run_11
091e79694da3        webiteasy_web       "/bin/bash"              4 days ago          Exited (137) 4 days ago                                                  webiteasy_web_run_10
3b6590de6b9d        webiteasy_web       "/bin/bash"              5 days ago          Exited (0) 4 days ago                                                    webiteasy_web_run_9
74b7def656e7        webiteasy_web       "/bin/bash"              5 days ago          Exited (137) 4 days ago                                                  webiteasy_web_run_8
9ecc07d08634        webiteasy_web       "/bin/bash"              5 days ago          Created                                                                  webiteasy_web_run_7
32bbbe9c03f0        mongo               "docker-entrypoint..."   5 days ago          Exited (0) 4 days ago                                                    test_db_1
e1d35ed19d30        webiteasy_web       "/bin/bash"              5 days ago          Exited (137) 5 days ago                                                  webiteasy_web_run_6
6a9e1606d119        webiteasy_web       "/bin/bash"              5 days ago          Created                                                                  webiteasy_web_run_5
851570a29cba        haskellchess_web    "runhaskell /haske..."   6 days ago          Exited (1) 6 days ago                                                    haskellchess_web_1
74b47d026757        webiteasy_web       "/bin/bash"              6 days ago          Exited (0) 5 days ago                                                    webiteasy_web_run_4
3e3c4d10e35a        85e96f61d536        "/bin/sh -c 'apk a..."   6 days ago          Exited (1) 6 days ago                                                    zealous_dijkstra
a79dc48005d8        bd38058e5097        "/bin/sh -c /srv/j..."   6 days ago          Exited (127) 6 days ago                                                  youthful_shirley
c09a63689135        bd38058e5097        "/bin/sh -c build.sh"    6 days ago          Exited (127) 6 days ago                                                  hardcore_spence
f8029d9226e1        haskell:latest      "/bin/bash"              7 days ago          Exited (137) 7 days ago                                                  haskellchess_web_run_1
$ docker-clean
cbcd659cf8a8
b6c16dabdc26
acd1631890fe
9ea333c98e6f
fb23ecb83d3b
887fbbad4c87
5fbe10194c42
091e79694da3
3b6590de6b9d
74b7def656e7
9ecc07d08634
32bbbe9c03f0
e1d35ed19d30
6a9e1606d119
851570a29cba
74b47d026757
3e3c4d10e35a
a79dc48005d8
c09a63689135
f8029d9226e1
$ docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                         NAMES
e560889ce3b9        webiteasy_web       "/bin/bash"              25 minutes ago      Up 25 minutes       4000/tcp, 0.0.0.0:3000->3000/tcp, 35729/tcp   webiteasy_web_run_16
f4fb9a6fda29        mdillon/postgis     "docker-entrypoint..."   2 hours ago         Up 2 hours          0.0.0.0:32768->5432/tcp                       copass_db_1
0fa3afb6d91d        webiteasy_web       "/bin/bash"              4 days ago          Up 4 days           4000/tcp, 35729/tcp                           webiteasy_web_run_14

This is only goodbye

Talk to you soon!