Docker Container and Vagrant / Virtualbox 101s

Some simple recipes for getting started with running some demo virtual applications in variety of ways.

Docker and Zeit Now – flaskdemo

This first demo creates a simple flask app and a Dockerfile with just enough to get it running; it runs locally if you have docker installed locally, and will also run on Zeit Now.

Create a new directory – flaskdemo – and create a couple of files in it, specifically:

flaskdemo.py

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello, World!'

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=5005)

Dockerfile

FROM python:3.6-slim-stretch

RUN pip3 install flask

EXPOSE 5005
COPY ./flaskdemo.py /var/flaskdemo.py
CMD ["python3","/var/flaskdemo.py"]

Download and run the Zeit Now application, then on the command line cd into the flaskdemo folder and type: now to launch the application and now ls to lookup the URL where it’s running.

If you prefer, and have docker installed locally:

  • on the command line, cd into the directory and then build the Docker container with the command docker build -t myflaskdemo . (the dot / . is important – it says “the path to the Dockerfile is the current directory)
  • run the container with: docker run -d -p 8087:5005 myflaskdemo and you should see a hello message on port 8087.

Compare this with what I had to do to get a flask app running via the Reclaim CPanel.

Note that as well as deploying to my local docker instance, the docker-machine command line application also allows me to launch remote servers (for example, on Digital Ocean) and then build/deploy the container there (old example). It’s not quite as easy as Zeit Now though…

Docker demo – Jupyter notebook powered API

This demo shows how to knock up an API server where the API is defined in a Jupyter notebook and published using the Jupyter Kernel Gateway (see also Building a JSON API Using Jupyter Notebooks in Under 5 Minutes). This is probably all overkill but it make for a literate definition of your API defining code…

Create a new directory – jupyterkgdemo and copy the following Jupyter notebook into it (gist/psychemedia/jupyterkgdemo.ipynb):

jupyterkgdemo.ipynb


Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

Add the following Dockerfile:

FROM python:3.5-slim-stretch

RUN pip3 install jupyter_kernel_gateway

EXPOSE 5055

COPY ./jupyterkgdemo.ipynb /var/jupyterkgdemo.ipynb
CMD ["/usr/local/bin/jupyter","kernelgateway","--ip=0.0.0.0","--KernelGatewayApp.api='kernel_gateway.notebook_http'","--KernelGatewayApp.seed_uri='/var/jupyterkgdemo.ipynb'", "--port=5055"]

With Docker installed locally:

  • on the command line, cd into the directory and then build the Docker container with the command docker build -t myjupyyterapidemo . (the dot / . is important – it says “the path to the Dockerfile is the current directory)
  • run the container with: docker run -d -p 8089:5055 myjupyyterapidemo and you should see a message on port 8089; go to localhost:8089/hello/whoever and you should get a message personalised to whoever.

Note that this doesn’t seem to run on Zeit Now; another port seems to be detected that I think breaks things?

Vagrant Demo

Vagrant is a tool that helps automate the deployment of virtual machines using Virtualbox. In some senses, it’s overkill; in others, it means we don’t have to provide instructions about setting up various Virtualbox bits and bobs…

Install vagrant and then add the following files to a vagrantdemo folder:

First, the simple flask app demo code we used before:

flaskdemo.py

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello, World!'

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=5005)

We can run this as a Linux service inside the VM – we means we need to provide a service definition file:

flaskservice.service

[Unit]
Description=Demo Flask Server

After=network.target

[Service]

Type=simple
PIDFile=/run/flaskdemo.pid

WorkingDirectory=/var/flaskdemo
ExecStart=/usr/bin/python3 flaskdemo.py

Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

To install the packages we need, and copy the local files into the correct locations inside the VM, we can define a config shell script:

flaskserver.sh

#!/usr/bin/env bash

apt-get update && apt-get install -y python3 python3-dev python3-pip && apt-get clean

pip3 install flask

THISDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

mkdir -p /var/flaskdemo
cp $THISDIR/flaskdemo.py /var/flaskdemo/
cp $THISDIR/flaskserver.service /lib/systemd/system/flaskserver.service

# Enable autostart
systemctl enable flaskserver.service

# Refresh service config
systemctl daemon-reload

systemctl restart flaskserver

Now we need a Vagrantfile to marshal the VM:

Vagrantfile

Vagrant.configure("2") do |config|
  config.vm.box = "bento/ubuntu-16.04"

  config.vm.network :forwarded_port, guest: 5005, host: 8077, auto_correct: true
  config.vm.synced_folder ".", "/vagrant"

  config.vm.provision :shell, :inline => <<-SH
    cd /vagrant/
    source ./flaskserver.sh
  SH
end

On the command line, cd into the folder and run vagrant up. You should be able to see the hello world on localhost:8077.

Note that as well as using vagrant to provision a VM using Virtualbox, other provisioners are available that would let me automatically fire up a server on a remote host (such as Digital Ocean using the Digital Ocean provisioner) and then run the same set-up script to build the machine there.

PS an example of running a crappy shiny demo (a file uploader) can be found via here. I'll add a demo to this post at some point, though not sure when… In testing, the R/shiny server image is too big to run under the Zeit Now free plan (The built image size (452.8M) exceeds the 100MiB limit).

Author: Tony Hirst

I'm a Senior Lecturer at The Open University, with an interest in #opendata policy and practice, as well as general web tinkering...