Get Started with Docker Today

STEP 1: Install Docker Engine - Community, a.k.a. docker-ce

See the full list of supported platforms here: https://docs.docker.com/install/#supported-platforms

Examples:

Follow the official installation instructions for your OS. Use the links above.

Subset of installation commands on Ubuntu:

ubuntu-docker-ce-install

STEP 2: Test your installation

Based on: https://docs.docker.com/get-started/part2/

If you've successfully installed Docker, you can test it using the following command:

$ sudo docker run hello-world

If successful, you should see something like the following as output:

Hello from Docker!  
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:  
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:  
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:  
 https://hub.docker.com/

For more examples and ideas, visit:  
 https://docs.docker.com/get-started/

TIP: If you can't run it the first time, try starting the Docker Daemon, dockerd, and then give it another shot:

$ sudo systemctl start docker

TIP: If you wish to enable the Docker Daemon to start at boot, you can use systemd or your init system to do so. For example, using systemd:

$ sudo systemctl enable docker

Create a containerized Python app

STEP 3: Create a Dockerfile

  1. Create a new, empty directory for your app: $ mkdir new_app ; cd new_app

  2. Create a 'Dockerfile'. This is used to define your app's environment, install dependencies, and so on.

  3. Paste the following into your Dockerfile:

# Use an official Python runtime as a parent image
FROM python:3.7.4-buster

# Set the working directory to /app
WORKDIR /app

# Copy the current directory contents into the container at /app
COPY . /app

# Set proxy server, replace host:port with values for your servers
# ENV http_proxy host:port
# ENV https_proxy host:port

# Install any needed packages specified in requirements.txt
RUN pip3 install --trusted-host pypi.python.org -r requirements.txt

# Make ports 80 available to the world outside this container
EXPOSE 80

# Define environment variable
ENV NAME World

# Run app.py when the container launches
CMD ["python", "app.py"]  

STEP 4: Define your dependencies

  1. Define your dependencies for this Python app by listing them out in a new file called 'requirements.txt'.

  2. Paste the following into 'requirements.txt':

Flask  
Redis  

STEP 5: Create the Python app

  1. Create your app. Call it 'app.py'.

  2. Paste the following into 'app.py':

from flask import Flask  
from redis import Redis, RedisError  
import os  
import socket

# Connect to Redis
redis = Redis(host="redis", db=0, socket_connect_timeout=2, socket_timeout=2)

app = Flask(__name__)

@app.route("/")
def hello():  
    try:
        visits = redis.incr("counter")
    except RedisError:
        visits = "<i>cannot connect to Redis, counter disabled</i>"

    html = "<h3>Hello {name}!</h3>" \
            "<b>Hostname: </b> {hostname}<br/>" \
            "<b>Visits:</b> {visits}"

    return html.format(name=os.getenv("NAME", "world"), hostname=socket.gethostname(), visits=visits)

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

Breakdown

  • Dockerfile defines the environment in which your app runs.
    • This includes the version of Python to use, in this case the python:3.7.4-buster image.
  • Docker installs the dependencies as instructed in your Dockerfile.
  • Pip is used to install the required packages listed in requirements.txt: Flask and Redis.
  • The app prints the environment variable NAME as well as the output from a call to socket.gethostname().

Gotchas

  • Redis isn't actually running yet. We installed the Python library, but not Redis itself.
  • Therefore, when running this app, we may get an error along the lines of:

Note: Accessing the name of the host when inside a container retrieves the container ID, which is like the process ID for a running executable.

Docker Magic

  • Without modifying the host environment or installing any additional packages on your system (besides Docker), we have layed the groundwork for a new Python app.
  • The next step is to build the Docker image.
  • NOTE: Neither building nor running the image will install anything extra on our host system.

STEP 6: Build the image

From the top level of your app directory (new_app), you should have:

$ ls
Dockerfile        app.py          requirements.txt  

Now you can build the Docker image. Give it a name using the --tag option.

The full command to build your image is:

$ sudo docker build --tag=friendlyhello .

If you need to make any modifications, you can run the above command again to overwrite the previous build.

Or, you can append a version number to the tag/image name, for example:

--tag=friendlyhello:v0.0.1

Once you've built your image, list it out, along with any other images you may have on your system, using $ sudo docker image ls. Example output:

REPOSITORY          TAG                 IMAGE ID            SIZE  
friendlyhello       latest              0bf4ae1c31e8        928MB  
python              3.7.4-buster        02d2bb146b3b        918MB  
hello-world         latest              fce289e99eb9        1.84kB  

STEP 7: Run the app

Run the app:

$ sudo docker run friendlyhello

  • If you get any errors relating to pip/pip3, or failures to install packages, you may need to modify your DNS settings for the Docker Daemon:
  • Edit your configuration file /etc/docker/daemon.json and append a dns key.
  • The example below shows Google's DNS servers:
{
  "dns": ["8.8.8.8", "8.8.4.4"]
}
  • Save the changes and then restart the Docker Daemon:
  • $ sudo systemctl restart docker
  • Now try running your app again: $ sudo docker run friendlyhello

TIP: Hit Ctrl+C to kill the app.

TIP: In order to access your app over the web or through your browser, you will need to map the specified HTTP port 80 for the container, to a port on the host system that is open to incoming connections.

STEP 8: Map to another port on the host machine

  • The container itself advertises that a service is running at the specified IP address using port 80. However, the host machine does not know or care about this (yet).
  • If you map the container port (80) to the host port (80), you will be able to 'visit' your app from your browser and see it running.
  • You can also map the app to a separate port on the host machine if you prefer.

Run your app image with the -p flag to indicate the desired port:

  • $ sudo docker run -p 80:80 friendlyhello
  • $ sudo docker run -p 4000:80 friendlyhello

STEP 9: Test the app

Now that you've initialized the app, you can visit the URL using the mapped port.

In your browser on the host machine, enter http://localhost:<PORT> to see your app running.

running-app

NOTE: If you are testing this on a web server or VPS, make sure the designated port is open to incoming connections, then try navigating to your app using the Public IP address of your server:

http://<PUBLIC-IP-ADDR>:<PORT>


OPTIONAL STEPS


Notes for users on a VPN:

From a local Linux machine running a VPN, I was not able to start the Docker Daemon and run the hello-world image initially. Read on to learn how to resolve this issue without modifying your current network or VPN configuration.

When I first tried running $ sudo docker run hello-world I got an error message about the daemon not running yet.

Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?  

So I tried $ sudo systemctl start docker, but got more errors:

systemd[1]: docker.service: Start request repeated too quickly.  
systemd[1]: Failed to start Docker Application Container Engine.  
Unit docker.service has failed  

Eventually, I found the suggestion to check the daemon logs under /var/log/daemon.log. There I found more revealing messages about what was going wrong with the dockerd:

dockerd[12144]: failed to start daemon:  
Error initializing network controller: list bridge addresses failed:  
PredefinedLocalScopeDefaultNetworks List:  
[172.17.0.0/16 172.18.0.0/16 172.19.0.0/16 172.20.0.0/16 172.21.0.0/16 172.22.0.0/16 172.23.0.0/16 172.24.0.0/16 172.25.0.0/16 172.26.0.0/16 172.27.0.0/16 172.28.0.0/16 172.29.0.0/16 172.30.0.0/16 172.31.0.0/16 192.168.0.0/20 192.168.16.0/20 192.168.32.0/20 192.168.48.0/20 192.168.64.0/20 192.168.80.0/20 192.168.96.0/20 192.168.112.0/20 192.168.128.0/20 192.168.144.0/20 192.168.160.0/20 192.168.176.0/20 192.168.192.0/20 192.168.208.0/20 192.168.224.0/20 192.168.240.0/20]: 
no available network  

And:

systemd-udevd[12309]: Could not generate persistent MAC address for docker0: No such file or directory  

Fortunately, user kinglion811 shared this suggestion on Docker GitHub issue #123 [see: original comment]. He wrote:

ip link add name docker0 type bridge  
ip addr add dev docker0 172.17.0.1/16  
can solve this issue  

So I checked my network interfaces on this local Linux machine...

# ip a:

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state [...]  
    inet 127.0.0.1/8 scope host lo

2: enp0s25: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc [...]  
    inet 192.168.<REDACTED>/<REDACTED> brd 192.168.<REDACTED> [...]

13: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> <REDACTED>  
    inet <REDACTED>/<REDACTED> scope global tun0

tun0 is a VPN network interface. But most importantly here: There is no docker0 interface. So let's create one as kinglion811 suggested:

  • # ip link add name docker0 type bridge
  • # ip addr add dev docker0 172.17.0.1/16

Now when I run ip a again I see a new interface for Docker:

[...]

14: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default qlen 1000  
    link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 scope global docker0
       valid_lft forever preferred_lft forever

Having created the docker0 interface and given it an IP address to use, I could start dockerd without issue: $ sudo systemctl start docker.

This solution worked for me on Debian; for others on Ubuntu it also solves the problem. See: https://github.com/docker/for-linux/issues/123

Up and docker running..!


If you have any questions or suggestions to improve this article, hit me up at [email protected] or on Twitter @kernelmastery (DMs are open).