I’m learning Docker. In this post I want to share a little experiment that I have done. I know the code looks like over-engineering but it’s just an excuse to build something with docker and containers. Let me explain it a little bit.
The idea is build a Time clock in the browser. Something like this:
Yes I know. We can do it only with js, css and html but we want to hack a little bit more. The idea is to create:
- A Silex/PHP frontend
- A WebSocket server with socket.io/node
- A Python script to obtain the current time
WebSocket server will open 2 ports: One port to serve webSockets (socket.io) and another one as a http server (express). Python script will get the current time and it’ll send it to the webSocket server. Finally one frontend(silex) will be listening to WebSocket’s event and it will render the current time.
That’s the WebSocket server (with socket.io and express)
var express = require('express'), expressApp = express(), server = require('http').Server(expressApp), io = require('socket.io')(server, {origins: 'localhost:*'}) ; expressApp.get('/tic', function (req, res) { io.sockets.emit('time', req.query.time); res.json('OK'); }); expressApp.listen(6400, '0.0.0.0'); server.listen(8080);
That’s our Python script
from time import gmtime, strftime, sleep import httplib2 h = httplib2.Http() while True: (resp, content) = h.request("http://node:6400/tic?time=" + strftime("%H:%M:%S", gmtime())) sleep(1)
And our Silex frontend
use Silex\Application; use Silex\Provider\TwigServiceProvider; $app = new Application(['debug' => true]); $app->register(new TwigServiceProvider(), [ 'twig.path' => __DIR__ . '/../views', ]); $app->get("/", function (Application $app) { return $app['twig']->render('index.twig', []); }); $app->run();
using this twig template
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Docker example</title> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> <link href="css/app.css" rel="stylesheet"> <script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script> <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script> </head> <body> <div class="site-wrapper"> <div class="site-wrapper-inner"> <div class="cover-container"> <div class="inner cover"> <h1 class="cover-heading"> <div id="display"> display </div> </h1> </div> </div> </div> </div> <script src="//localhost:8080/socket.io/socket.io.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script> <script> var socket = io.connect('//localhost:8080'); $(function () { socket.on('time', function (data) { $('#display').html(data); }); }); </script> </body> </html>
The idea is to use one Docker container for each process. I like to have all the code in one place so all containers will share the same volume with source code.
First the node container (WebSocket server)
FROM node:argon RUN mkdir -p /mnt/src WORKDIR /mnt/src/node EXPOSE 8080 6400
Now the python container
FROM python:2 RUN pip install httplib2 RUN mkdir -p /mnt/src WORKDIR /mnt/src/python
And finally Frontend contailer (apache2 with Ubuntu 16.04)
FROM ubuntu:16.04 RUN locale-gen es_ES.UTF-8 RUN update-locale LANG=es_ES.UTF-8 ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update -y RUN apt-get install --no-install-recommends -y apache2 php libapache2-mod-php RUN apt-get clean -y COPY ./apache2/sites-available/000-default.conf /etc/apache2/sites-available/000-default.conf RUN mkdir -p /mnt/src RUN a2enmod rewrite RUN a2enmod proxy RUN a2enmod mpm_prefork RUN chown -R www-data:www-data /mnt/src ENV APACHE_RUN_USER www-data ENV APACHE_RUN_GROUP www-data ENV APACHE_LOG_DIR /var/log/apache2 ENV APACHE_LOCK_DIR /var/lock/apache2 ENV APACHE_PID_FILE /var/run/apache2/apache2.pid ENV APACHE_SERVERADMIN admin@localhost ENV APACHE_SERVERNAME localhost EXPOSE 80
Now we’ve got the three containers but we want to use all together. We’ll use a docker-compose.yml file. The web container will expose port 80 and node container 8080. Node container also opens 6400 but this port is an internal port. We don’t need to access to this port outside. Only Python container needs to access to this port. Because of that 6400 is not mapped to any port in docker-compose
version: '2' services: web: image: gonzalo123/example_web container_name: example_web ports: - "80:80" restart: always depends_on: - node build: context: ./images/php dockerfile: Dockerfile entrypoint: - /usr/sbin/apache2 - -D - FOREGROUND volumes: - ./src:/mnt/src node: image: gonzalo123/example_node container_name: example_node ports: - "8080:8080" restart: always build: context: ./images/node dockerfile: Dockerfile entrypoint: - npm - start volumes: - ./src:/mnt/src python: image: gonzalo123/example_python container_name: example_python restart: always depends_on: - node build: context: ./images/python dockerfile: Dockerfile entrypoint: - python - tic.py volumes: - ./src:/mnt/src
And that’s all. We only need to start our containers
docker-compose up --build -d
and open our browser at: http://localhost to see our Time clock
Full source code available within my github account
Reblogged this on Florin LinuCS. CS stands for Computer Science, occasionally Counter-Strike..
Hello, very interesting! Just my two cents 😉
– As the compose file talks about ‘composition’ of containers, I would hide container implementation details on it. I mean, better specify container entrypoint on Dockerfiles (ENTRYPOINT sentence).
– You can group RUN operations on Dockerfile to generate less layers and smaller containers. For example, instead of three RUN sentences invoking apt-get, have only one RUN apt-get update && apt-get install && apt-get clean.
Thanks. I use entrypoint in docker-compose especially because node containers. I prefer to put code in a mounted container (and share it between containers). That means that code isn’t available (I cannot run npm start) until docker-compose is executed. Anyway I agree with you.