Docker 01: first 3 steps of your web app — Pragmatic Docker series
A 7 minutes story written on Sep 2018 by Adrian B.G.
Containers in general, but Docker in particular is a concept harder to grasp. I will try to write the simplest and most straightforward tutorial for a first hands-on experience for a developer.
Pragmatic Docker series for developers
- Docker 01 — first 3 steps of your web app (this article)
- Docker 02 — meet bash, variables and logs
- Docker 03 — persistence and compose multiple services (TODO)
- Docker 04 — hubs, deployments and cloud (TODO)
Containers for everyone
The size of this tutorial may seem scary but it is all boilerplate, I only introduce 3 new commands:
docker create network
,docker build
anddocker run
.
To keep the tutorial simple I will presume you already have:
- Bash (even on windows)
- basic knowledge on Linux, Docker and bash/CLI
- installed docker at least version 15 and git
- basic knowledge on developing back-end web services
- ~200MB free space on HDD (for the images)
Optional you should:
- be able to run
docker
commands with your user (without sudo) OR add**sudo**
to all commands and scripts from this tutorial - allow HTTP traffic (port 80) if you use Google Cloud, AWS … and you want to access the web service trough a browser or remote> To learn about Docker I suggest the following resources: docker.com, IBM essentials, training.docker.com.This tutorial assumes that you already have a basic knowledge about Docker, hence the name “pragmatic”.
Docker terms #likeimfive
- Docker — collection of tools to deploy and run your apps
- Image — An executable blueprint for containers, a Class in OOP, a wrapper for your all app dependencies, code and configs
- Container — a set of isolated Linux processes in a bubble, an Object in OOP, the run-time instance of an Image
- Network — is like your Wireless home network where Docker is the router
- Volume — a portion of your hard-disk that can be accessed by a container
What are we going to do today
- Create a private docker network
- Run a container with a popular database (memcached in this case)
- Run a container with a simple web service that “talks” to the database> TL;TR: You will find all the scripts, configs with comments, and the web app source in this repository. The files are heavily annotated for a better understanding of how Docker works and how your code is affected by it.
Network
We need a network in order for our containers to “talk” to each other. It will behave like a “bubble”, it will surround our containers and protect them from the outside “world”, and vice-versa. Imagine that we will create a WiFi network and Docker is the router.
In older docker tutorials you will notice the usage of the
link
param of thedocker run
command, but that feature is deprecated since late 2015.
Before we create our (running) containers we must build a “home” for them, a local private network. It sounds complicated but docker makes it easy for us:
$ docker network create tut-network
$ docker network ls #list all the networks, to confirm it was created``
After this step, all the containers we will create will have the following parameter docker run .... --network tut-network ....
. All the containers that are not part of this network cannot access their open ports, including your machine (localhost), for security reasons. In order to gain access to the web app we will use another docker run
parameter that links a container port to a (localhost) port: --publish localhost:myport:containerport
.
The database
This tutorial only covers one service, namely memcached for simplicity reasons, you can repeat these steps with any other technology: MySQL, PhpMyAdmin, MongoDB you name it. You can use the search command or visit hub.docker.com: docker search "mysql"
to find more images.
We will keep the memcached port private, for security reasons. Only containers that run in our (newly) created docker private network tut-network
will have access to it. You can access the full bash script here.
$ docker run `#main command, run a container on my machine` \
--name tut-memcache `#unique name so you can access it later` \
--detach `#detach the process from the current bash` \
--network tut-network `#connect it to the private network` \
--restart=on-failure:3 `#if the memcached process crashes, docker will restart the container 3 times max` \
memcached:1.5.10-alpine `#the name of the image, see https://hub.docker.com/_/memcached/, alpine is small`
With docker ps
you should see the tut-memcache container running.
docker ps
... STATUS PORTS NAMES
... Up 2 seconds 11211/tcp tut-memcache
Memcached is a popular technology, so we used a pre-built image generated by the memcached developers.
The Image
Our app on the other hand is not so popular either so public, so we will have to build our own image (blueprint), before we can run it in a container.
For the app:
- you can build your own web app that talks to
tut-memcache:11211
- or you can use my simple Node app (JS for popularity reasons) that does NOT require NPM. You can clone
github.com/bgadrian/docker-tutorials
repo.
Because we will run the app in a container you don’t even need to have Node installed on your system.
Regarding the app, I kept everything as simple as I could. In order to use the memcached storage, at every request the web server receives, it increments a numerical value from the database: cache.increment("pageviews",1,0);
.
Now we will take our app, dependencies and config files and copy them in a docker Image. The easiest (native) way to build an image is to have a Dockerfile:
FROM node:8.12.0-slim
EXPOSE 80
ADD memjs ./memjs
COPY app.js .
CMD node app.js
This config file (download here) will “tell” docker to build an image that has NodeJS, with an open port 80, to copy the memjs folder (vanilla memcached client) and the app.js file inside the image. The last command is the entry-point of the container (main process). To get a local copy of the project you can clone it:
$ git clone https://github.com/bgadrian/docker-tutorials.git
$ cd docker-tutorials/01-first-steps
Next step is to build the docker image, run this command in the folder that contains the Dockerfile.
`$ docker build \
-t tut-app-image:v1 `#tag it with this name:tag` \
-t tut-app-image:latest `#make it the latest version too` \
. `#build with the instructions found on ./Dockerfile`
$ docker images `#to list all your docker images`
If you encounter permissions errors like
[context](https://docs.docker.com/install/linux/linux-postinstall/#configure-docker-to-start-on-boot)
see this page.
Now we have an executable image (tut-app-image), that contains everything the app needs to run. This allows us to run the app on any computer that has Docker, without polluting the system with its dependencies and files.
The container
$ docker run \
--name tut-app `#put it an unique name so you can access it later` \
--detach `#detach the process from the current bash` \
--network tut-network `#connect it to the private network we created, --link is deprecated` \
--restart=on-failure:3 `#if the app.js crashes, docker will restart the container 3 times max` \
--publish 0.0.0.0:80:80 `#link EXPOSEd port from Docker file to your localhost` \
tut-app-image:latest `#the name of the image:tag`
Running docker ps
should confirm that the container is running. By accessing http://localhost (or your public ip for VMs) should increase a key stored in memcached, proof that the 2 containers can “speak” to each other.
$ curl localhost
Hello from app.js! Request count: 0
$ curl localhost
Hello from app.js! Request count: 1
$ curl localhost
Hello from app.js! Request count: 2
Now you can modify the app.js file (in any way you want, go ahead I’ll wait ….). After that, you will have to also update the image and the container. To ease these operations I wrote a script that builds and run the app: $ ./d-run-app.sh
Do not forget to READ all the files from my repo, they are heavily annotated, hopefully it will help understand what each command and parameter does.
Operational commands
docker start|stop|restart|kill tut-app
docker ps -a
— list all your containersdocker images
— list all your images, don’t forget to clean them updocker network ls
— all your networksdocker network inspect tut-network
— detailed view of a networkdocker inspect tut-app
— everything about a specific containerdocker logs -f tut-app
— tailing your app logs
Notes:
- if you already have a server on port 80 you can run the app on 8080 or any other port:
--publish 0.0.0.0:8080:80
- if you do not want to expose the server to the public change the binding ip to your localhost only:
--publish 127.0.0.1:80:80
- I used the memjs client, thanks to amit levy for building it
- If you use Windows I suggest moving to a more developer-friendly system
- Use the [] (https://github.com/bgadrian/docker-tutorials/blob/master/1.tutorial-simple/d-cleanup.sh)`[d-cleanup.sh](https://github.com/bgadrian/docker-tutorials/blob/master/1.tutorial-simple/d-cleanup.sh)` [script] (https://github.com/bgadrian/docker-tutorials/blob/master/1.tutorial-simple/d-cleanup.sh)to purge this tutorial from your system
- If you build your own web service you have to create a new Dockerfile in a new folder