Red Green Repeat Adventures of a Spec Driven Junkie

Migrating from Vagrant to Docker - Part 2

I want to continue from my first article on migrating from vagrant to docker and add more commands.

I will walk through specific vagrant commands and their docker equivalent in my normal vagrant workflow.

You will learn the docker run commands to: share data, expose ports, stopping systems, and destorying systems. I’ll also share a quick one-liner to start and connect to a docker image without looking up any container IDs (nor use an output of another command!)

This article will take you about seven minutes to read.

Abd al-Qadir Hisari - Calligraphic Galleon source and more information

Introduction

In part one of my migration from vagrant to docker, I shared docker equivalent commands for common vagrant commands I used, such as:

  • selecting guest operating system
  • creating system
  • running system
  • logging into system

Those commands will get one pretty far in operating virtualized systems. This article will go further and cover:

  • command to have data share between the systems
  • command to expose ports
  • command to shutting down the system
  • shortcut: from image to connecting to running container

This embodies my normal workflow with vagrant and help me transition to docker full time.

Sharing Data

When I first used VirtualBox, the technology under vagrant, I loved having a guest operating system inside another.

After getting vagrant running and it auto-magically connected a shared folder for me, I could not think of working with a virtualized system that does not easily share data.

vagrant

The default folder shared is the current directory of the host system into the /vagrant directory on the guest system.

Easiest way to check if this is working: within the guest system, run: ls /vagrant and Vagrantfile should be one of the files listed.

docker

The default configuration for docker does not share any directories between guest and host by default. The user must specify this -v option on docker run. The command to connect the current guest directory to the /docker directory on the guest system:

docker run -v $(pwd):/docker <tag name>

As the current guest directory can contain anything, such as the Dockerfile to create the guest system, source code for your project, etc. validating the connection is trickier.

I would suggest the first time running the above command to have files in the current directory for easy validation when connecting with docker exec

Exposing Ports

Having a self contained system that has access through specific ports, whether it’s SSH (to connect and run commands) or even application ports, like 80/443 for web servers allows even better local development experience.

vagrant

To expose ports on a system managed by vagrant, one would have to add the following configuration to the Vagrantfile:

  config.vm.network "forwarded_port", guest: 80, host: 8080

So the whole Vagrantfile would be:

Vagrant.configure('2') do |config|
  config.vm.box = 'ubuntu/focal64'
  config.vm.network "forwarded_port", guest: 80, host: 8080
end

This exposes the guest application port, say nginx webserver’s port 80, to the host’s port 8080. This makes loading: http://localhost:8080 from the host system.

docker

I have usually configured which ports to expose in docker using docker run command option:

docker run -p <host>:<guest> <tag name>

To set host’s port 8080 to pooint to a guest’s port 80:

docker run -p 8080:80 <image tag>

Shutting Down

When you’re done using the system, time to shut them down or they will continue to allocate resources!

vagrant

From the host system’s folder, shutting down a vagrant managed system requires running:

vagrant halt

Simple, right?

docker

Shutting down a running docker container requires knowing the container ID. One can look up the running container using: docker ps:

$ docker ps
CONTAINER ID   IMAGE          COMMAND      CREATED         STATUS         PORTS   NAMES
0f7968bf4078   ubuntu_focal   "sleep 1h"   7 seconds ago   Up 6 seconds           brave_cori

With the container ID, run: docker kill

docker kill <container ID>

$ docker kill 0f79
0f79

$ docker ps
CONTAINER ID   IMAGE   COMMAND   CREATED   STATUS   PORTS   NAMES

Instead of manually looking up the container ID, use the following to kill any docker container running a specific tag.

docker kill $(docker ps | grep <tag> | awk '{ print $1 }')

Replace <tag> with the image tag you want killed.

Destroying

Once in awhile, there is a need to remove a system. docker and vagrant manage the files so there are specific commands to remove files.

vagrant

From the same folder as the Vagrantfile of the system to remove, run:

vagrant destroy

and follow the prompts. Note: this command deletes the data in the guest system, leaving items in the Vagrantfile folder untouched.

docker

From anywhere in the host system, run:

docker images

to get a list of images for guest systems created.

$ docker images
REPOSITORY                TAG       IMAGE ID       CREATED       SIZE
<none>                    <none>    0fbb6d52aaba   5 days ago    65.6MB
ubuntu_focal              latest    325acabf7223   5 days ago    65.6MB

In docker, images are the base which containers use. (i.e. you can have multiple containers running the ubuntu_focal image - all doing different things.)

To remove an image, run:

docker image rm

$ docker image rm ubuntu_focal
Untagged: ubuntu_focal:latest
Deleted: sha256:325acabf7223b5eac18f9f2bb445325adfd7882f896bfc378f845488aff768fb

Ta-da, done!

referenced image???

There can be issues removing an image when running: docker image rm:

$ docker image rm ubuntu_focal
Error response from daemon: conflict: unable to remove repository reference "ubuntu_focal" (must force) - container 98b09714e4d8 is using its referenced image 325acabf7223

Yet, all the containers have stopped:

$ docker ps
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

What gives??

docker ps -a

There is (yet) another docker command to list all the containers, even stopped ones:

docker ps -a

Stopping a container doesn’t guarantee removal of the associated resources. Those resources may remain in the system for future utilization.

$ docker ps -a
CONTAINER ID   IMAGE                     COMMAND                  CREATED       STATUS                     PORTS      NAMES
fe53decec07a   ubuntu_focal              "/bin/bash"              3 days ago    Exited (127) 3 days ago               silly_brattain
f52671db7e5c   ubuntu_focal              "/bin/bash"              3 days ago    Exited (0) 3 days ago                 boring_zhukovsky
98b09714e4d8   ubuntu_focal              "sleep 5d"               4 days ago    Exited (137) 3 days ago               suspicious_wescoff

In the example, only using docker ps -a lists the 98b09714e4d8 container that still references ubuntu_focal image. Hence, docker image rm prevents removal.

container prune

To clean up all container resources, even ones listed by docker ps -a, run:

docker container prune

$ docker container prune
WARNING! This will remove all stopped containers.
Are you sure you want to continue? [y/N] y
Deleted Containers:
fe53decec07a645abbcf811f240b91ef7bb4b3775b071588dd289efa994086cd
f52671db7e5c08c44c2cd9d822a662af6e5019b83f90c40a775f8ec64807a571
98b09714e4d88164a68800d6de2edbcacdb6572f400100a1b9d205343c1a81d7

Total reclaimed space: 1.042GB

This cleaned up the container running ubuntu_focal image and now docker image rm ubuntu_focal succeeds.

Can I do Better?

For docker, the commands presented work to run and connect into a system works, they are a bit tedious:

  1. run the system: docker run <tag>
  2. connect into the system: docker exec -it $(docker ps | grep <tag> | awk '{ print $1 }') <command>

While, this is comparable to vagrant’s commands:

  1. run the system: vagrant up
  2. connect into the system: vagrant ssh

Two steps is tight. Can I do better?

To do better for the docker system, there would have to be one step.

And there is!

I have found an even easier way to run and connect into a system managed by docker:

docker run -it <tag> <command>

So, the command for running the /bin/bash command on the ubuntu_focal image would be:

docker run -it ubuntu_focal /bin/bash

$ docker run -it ubuntu_focal /bin/bash
root@bef07f57852f:/# ls
bin  boot  dev  etc  home  lib  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var

You can chain other options, exposing ports, sharing data, with this command too:

docker run -v <host path>:<guest path> -p <host port>:<guest port> -it <image> <command>

So, to share the current directory to the /docker directory and connect host port 8080 to the guest’s 80 port on the ubuntu_focal image:

$ docker run -v $(pwd):/docker -p 8080:80 -it ubuntu_focal /bin/bash
root@b1101dbf0cc1:/# ls
bin  boot  dev  docker  etc  home  lib  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
root@b1101dbf0cc1:/# ls docker
404.html  CNAME  Dockerfile  Gemfile  Gemfile.lock  LICENSE.md  README.md  Vagrantfile  _config.yml  _drafts  _includes  _layouts  _posts  _sass  _site  about.md  contact.html  docker-compose.yml  index.html  public  scripts  shortcuts.org  styles.scss  vagrant_scripts

Isn’t that better?

Command Summary

The following table lists the commands covered for working with vagrant and docker managed systems:

  vagrant docker
selecting guest operating system see Vagrantfile in part 1 see Dockerfile in part 1
creating the system vagrant up docker build . -t <tag name>
running the system† vagrant up docker run <tag>
connecting into the system† vagrant ssh docker exec -it <container SHA> /bin/bash
sharing data† none needed docker run -v <host path>:<guest path> <tag name>
exposing ports† see Exposing Ports section docker run -p <host port>:guest port> <tag name>
shutting down vagrant halt docker kill <container SHA>
destroying system vagrant destroy docker image rm

† - Have one docker command for all of these activities:

docker run -v <host path>:<guest path> -p <host port>:<guest port> -it <image> <command>

Conclusion

docker and vagrant are two virtualized systems management. Starting from vagrant, I have a good understanding of how it works conceptually. Stepping into docker, I didn’t have such an easy time until creating this map of vagrant:docker commands.

Now I feel confident in getting a docker system going for my day-to-day.