Docker can be best described as a lightweight virtual machine. However, it technically is a virtual environment. Unlike products such as VirtualBox, VMWare, and Parallels that require virtual hardware (BIOS, HDDs, NICs, etc), guest operating system, drivers, libraries, binaries, and a Hypervisor, Docker only requires programs and libraries to run an application.
I won't go into installing it on Linux as it's pretty well documented by the different distributions as well as on Docker’s site. Currently, Windows 10 Pro (Enterprise and Education) is the only version supported save for Windows Server. When installing Docker on MacOS you must have VirtualBox installed, which negates the resource benefits provided by Docker running on Linux.
Running a container is easy. When using the run command, if you don't have the image you specified in the command Docker will download the latest version of that image.
Unable to find image 'drupal:latest' locally Trying to pull repository docker.io/library/drupal ... Pulling repository docker.io/library/drupal
By default, the container name is a random. Using the the --name switch allows you to specify a more desirable name.
If the image does not exist or needs to be updated, docker will search for the image and download it from the Docker Hub. Alternatively, you can specify a repository, image, and tag if you need a specific image for a container.
$ docker run --name="drupal-7" docker.io/drupal:7
If you see the following error message when trying to pull an image from Docker:
Unable to find image 'docker.io/drupal:7' locally Trying to pull repository docker.io/library/drupal ... docker: Get https://registry-1.docker.io/v2/library/drupal/manifests/7: Get https://auth.docker.io/token?scope=repository%3Alibrary%2Fdrupal%3Apull&service=registry.docker.io: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers). See 'docker run --help'.
Restart the Docker server. The only time I've seen this error is when my DNS servers change. This is more prone to happen if you are working on a laptop moving from one coffee shop to another.
# systemctl restart docker
One thing you can try doing is adding an entry for index.docker.io to your /etc/hosts file by running the following command:
# dig index.docker.io +noall +answer +nocomments | grep ' A ' | cut -d' ' -f 5 | xargs echo "index.docker.io " >> /etc/hosts
Working with Containers
The run command offers the most options of all the commands the following are most likely to be used.
-d --detach Run the container in the background and print the container ID:
To attach to a container, the attach command is issued with either the name or container id:
$ docker attach 6726abe22e57ae596946b796ec2d40068756935914652425e274157b66427b67
--rm automatically removes the container when it exists. This flag cannot be used in conjunction with the -d, --detach. When the container exits it is removed freeing the name.
$ docker run --rm docker.io/drupal:7
-t --tty Allocate psudo TTY. This is typically used in combination with the -i (interactive) option.
$ docker run docker.io/drupal:7
--pid="name|id" Allows you to share the process namespace. You can share the process namespace with other containers or the host system, which can be beneficial for debugging purposes.
$ docker run --pid="host" docker.io/drupal:7
Using the --restart option and specifying a policy of no (default), on-failure:<NUM_TRIES>, always, or unless-stopped determines how docker will handle a container when it stops.
Without any resource constrains specified, the container is able to consume as much of the system resources as it needs.
|-m, --memory=""||Memory limit (format: <number>[<unit>]). Number is a positive integer. Unit can be one of b, k, m, or g. Minimum is 4M.|
|--memory-swap=""||Total memory limit (memory + swap, format: <number>[<unit>]). Number is a positive integer. Unit can be one of b, k, m, or g.|
|--memory-reservation=""||Memory soft limit (format: <number>[<unit>]). Number is a positive integer. Unit can be one of b, k, m, or g.|
|--kernel-memory=""||Kernel memory limit (format: <number>[<unit>]). Number is a positive integer. Unit can be one of b, k, m, or g. Minimum is 4M.|
|-c, --cpu-shares=0||CPU shares (relative weight). The default weight of each container is 1024. In a system with one CPU core running a container with a weight of 1024, the full core will be used for the container if it needs it. With the same system that is running two containers and with the same weight will consume 50% of the CPU when both containers are running at full speed. In the event that one container doesn't need a lot of CPU power, one container can use any resources not required by other containers.|
|--cpu-period=0||Limit the CPU CFS (Completely Fair Scheduler) period|
|--cpuset-cpus=""||CPUs in which to allow execution on (0-3, 0,1)|
|--cpuset-mems=""||Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems.|
|--cpu-quota=0||Limit the CPU CFS (Completely Fair Scheduler) quota|
|--blkio-weight=0||Block IO weight (relative weight) accepts a weight value between 10 and 1000.|
|--blkio-weight-device=""||Block IO weight (relative device weight, format: DEVICE_NAME:WEIGHT)|
|--device-read-bps=""||Limit read rate from a device (format: :<device-path>:<number>[<unit>]). Number is a positive integer. Unit can be one of kb, mb, or gb.|
|--device-write-bps=""||Limit write rate to a device (format: :<device-path>:<number>[<unit>]). Number is a positive integer. Unit can be one of kb, mb, or gb.|
|--device-read-iops=""||Limit read rate (IO per second) from a device (format: <device-path>:<number>). Number is a positive integer.|
|--device-write-iops=""||Limit write rate (IO per second) to a device (format: <device-path>:<number>). Number is a positive integer.|
|--oom-kill-disable=false||Whether to disable OOM Killer for the container or not.|
|--oom-score-adj=0||Tune container’s OOM preferences (-1000 to 1000)|
|--memory-swappiness=""||Tune a container’s memory swappiness behavior. Accepts an integer between 0 and 100.|
|--shm-size=""||Size of /dev/shm. The format is <number><unit> number must be greater than 0. Unit is optional and can be b (bytes), k (kilobytes), m (megabytes), or g (gigabytes). If you omit the unit, the system uses bytes. If you omit the size entirely, the system uses 64m.|
docker run --name="drupal" --memory="512M" --memory-swap="-1" --memory-reservation="256M" docker.io/drupal:7
The above command will create a new container named drupal with a hard memory limit of 512MB, a 256M soft limit, and unlimited swap.
|--security-opt="label=user:USER"||Set the label user for the container|
|--security-opt="label=role:ROLE"||Set the label role for the container|
|--security-opt="label=type:TYPE"||Set the label type for the container|
|--security-opt="label=level:LEVEL"||Set the label level for the container|
|--security-opt="label=disable"||Turn off label confinement for the container|
|--security-opt="apparmor=PROFILE"||Set the apparmor profile to be applied to the container|
|--security-opt="no-new-privileges"||Disable container processes from gaining new privileges|
|--security-opt="seccomp=unconfined"||Turn off seccomp confinement for the container|
|--security-opt="seccomp=profile.json"||White listed syscalls seccomp Json file to be used as a seccomp filter|
Security labeling can be overridden by using the --security-opt option. For example, the context of the process running, /bin/bash, in the container can be modified to use the httpd_sys_content_rw_t context:
$ docker run --name "drupal" --security-opt label=type:httpd_sys_content_rw_t -it drupal:7 /bin/bash
Docker's ps command shows the container's state; running or exited with code. Running the ps command without the -a option will show only the running containers.
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES dc029e8b5e3f docker.io/mysql:latest "docker-entrypoint.sh" 6 hours ago Up 6 hours 3306/tcp mysql cfe211de99af docker.io/drupal:7 "apache2-foreground" 6 hours ago Up 6 hours 0.0.0.0:8080->80/tcp drupal
The CONTAINER ID is a unique ID of the running container. The IMAGE column tells you what image the container is running. COMMAND tells you what command is being run inside the container. The CREATED, STATUS, and NAMES are self explanatory. The PORTS column will list all ports that the container uses and what port they are mapped to on the host. In the above output, the container mysql, which runs mysql is not accessible from the host, but the container drupal is accessible via port 8080.
In order to remove a container it must be stopped and to remove an image the container must be removed.
$ docker run -d --name drupal docker.io/drupal:7
$ docker stop drupal
$ docker rm drupal
$ docker rmi docker.io/drupal:7
When a container is stopped, all modifications made to files will remain and be available the next time the container is started. Removing the container will also remove any file changes.
By default, all containers will use the bridged interface setup by Docker, usually called docker0 and assigned an IP address on that interface's network, on my machine the addresses that are assigned are 172.17.0.x. The default hostname of the container is the container's ID. Setting specific DNS settings for the container typically isn't necessary as the DNS settings are transferred from the host to the container. However, this is dependent on the network environment.
|--dns=||Set custom DNS servers.|
|--dns-search=||Set custom DNS search domains.|
|-h, --hostname=<HOSTNAME>||Sets the hostname of the container. By default the hostname will be the first 12 characters of the container's ID. When specifying a hostname make sure that it resolves to the IP of the container.|
|-P, --publish-all||publishes all of the container's servers ports to random host ports. To get the randomly assigned host port use Docker's ps command.|
|-p, --publish=||Publish a specified container port or ports to host. This option can be used multiple times for each port the container uses. The mapping format is <host_port>:<container:port>|
|--add-host="<name>:<address>"||Takes both the name and address and adds it to the hosts file, in this case, the entry of 127.0.0.1 dev.paulusworld.com was added to the hosts files.|
|--net=""|| Has several possible values:
$ docker run -d --name "drupal" --dns="188.8.131.52" --dns-search="google.com" --hostname="www.paulusworld.com" -p 8080:80 --add-host="dev.paulusworld.com:127.0.0.1" docker.io/drupal:7
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES d23457accc8b docker.io/drupal:7 "apache2-foreground" About a minute ago Up About a minute 0.0.0.0:8080->80/tcp drupal
$ docker exec -it drupal /bin/bash
search google.com nameserver 184.108.40.206
127.0.0.1 localhost ::1 localhost ip6-localhost ip6-loopback fe00::0 ip6-localnet ff00::0 ip6-mcastprefix ff02::1 ip6-allnodes ff02::2 ip6-allrouters 127.0.0.1 dev.paulusworld.com 172.17.0.2 www.paulusworld.com www
In the above example, the container will have the name of drupal using Google's DNS servers. The host name of the container will be set to www.paulusworld.com with an entry of dev.paulusworld.com pointing to the localhost of the container in the /etc/hosts file.
Running a docker container is just a portion of the larger picture. Although it's possible to have a container running multiple services such as Apache, MariaDB, and Solr it's best to have separate containers for each service. For that there is a tool called Docker compose. To create custom Docker images, you can write a Dockerfile and run the build sub-command. Both topics I just mentioned are topics that will be addressed separately.