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.
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:
- run the system:
docker run <tag>
- connect into the system:
docker exec -it $(docker ps | grep <tag> | awk '{ print $1 }') <command>
While, this is comparable to vagrant’s commands:
- run the system:
vagrant up
- 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.