The latest expert opinions, articles, and guides for the Java professional.
Best practices are important, but often overlooked in a world overloaded with information. This is why presenting information in a digestible form is really helpful. We’ve released a couple of cheat sheet on various topics ranging from Java 8 best practices, to advice on how to use Java 8 streams, to the most useful git commands and workflows.
This time we’re going to talk about Docker. The sparkling unicorn of containerization happening in the world of software development and the amazing tool that can simplify the configuration of your projects tenfold.
We considered how we used Docker, what are the current best practices and what commands we need to google most frequently. Then we combined this data into a cheat sheet, so you can also benefit from this work.
Let’s now go into more details about what is great about Docker and what commands you’re most likely to use daily. Also, if you don’t mind, I’ll be incredibly happy, if you share it with your colleagues and friends:
— Oleg Šelajev (@shelajev) March 17, 2016
One Docker to Rule Your Containers
Docker is an open platform to develop, ship, and run applications, more commonly known as a container manager. What’s a container you ask? A container is an encapsulated environment, which runs on top of a very shallow level of abstractions, providing a virtual machine like isolation for running processes. Now if you are just started with Docker, it’s important we define the vocabulary that you’ll need to learn as well as a number of utilities you need to master to become proficient with Docker.
Let’s start with the terms. In a nutshell Docker uses images to specify what the container infrastructure should look like for you to run your apps in it.
- Layer – a set of read-only files to provision the system. Think of a layer as a read only snapshot of the filesystem.
- Image – a read-only layer that is the base of your container. It can have a parent image to abstract away the more basic filesystem snapshot. So a Java image would inherit from a linux image with the preinstalled utilities. A tomcat image will have a Java image as the parent because it depends on Java to run Tomcat.
- Container – a runnable instance of the image, basically it is a process isolated by docker that runs on top of the filesystem that an image provides.
- Registry / Hub is the central place where all publicly published images live. You can search it, upload your images there and when you pull a docker image, it comes the repository/hub.
- Docker machine is a VM within which you can run Docker containers. On Linux you can run docker containers natively, but on OSX and Windows you need a layer of abstraction. A docker machine will spin a very lightweight virtual machine that integrates with the docker command line utilities really well.
- Docker compose is a utility to run multiple containers as a system of containers. It will take care of making them aware of each other and ensure they’re properly connected to each other. This means you can run your application in one container and your database in a different container, and your analytics application in a different container, and so on. This is the ultimate isolation and it means that your applications are independent and are run in development in a very similar way to how the system might work in production.
Now with the basic terminology cleared, it’s time to look at the command line utilities that will make you love Docker.
Docker machine is a utility that lets you install Docker on virtual hosts, and then manage those hosts from the command line. Why should developers be interested in docker machine? It is currently the preferred way to run Docker on OSX and Windows. It also helps you to provision and manage docker containers on a network of machines, but that is less interesting from the developer’s point of view.
Docker machine will create a tiny virtual machine that is capable or running docker itself.
After installing the machine you run a command like:
docker-machine start default
And your virtual machine named default is ready. However, what you also want is to make docker on the host system understand that it has to work with that virtual machine. To do that you’ll run:
docker-machine env default eval "$(docker-machine env default)"
This command will configure your command line environment variables that will help you use docker with a particular docker-machine, in our case default, without additional complexity. As a developer you’ll rarely need anything else from the docker machine, you can list the available machines with the
docker-machine ls command and start or stop any particular of them by calling
docker-machine start|stop machine-name.
And now we’re finally ready to dive into the heaven of containerized applications.
Building a Container
A docker container is basically a process or a set of processes running in isolation with a predefined provisioned file system. Think about it this way, a container is a collection of processes that have access to the files in the image.
Images are loaded from the registry, where they are publicly available to anyone. They form a hierarchy, so images can have dependencies on other images. This is really convenient, as your development platform images can depend on the image with common OS utilities.
docker pull image_name:tag
The pull command will download the image and all its parents needed to create the containers with that image. The tag parameter is usually used as the part of the name, but if your container evolves, or includes different versions of the software used, it’s nice to tag them without changing the base image name.
docker create --name container_name image_name:tag
The create command is used to instantiate the container from the image. You almost always want to name it by providing the –name parameter. To start and stop the container, naturally, you’ll use the
docker start container_name and
docker stop container_name commands respectively. Also, almost always when there’s a stop command, there’s a
kill command too, to terminate the process less graciously.
Now there’s a shorthand for create and start command, which is the run. It will create the container and run it, which is really useful for the various one-liners. We’ll look at them in the next section of this post.
Sometimes you will want to create your own images, either to dockerize your project setup or just because you can. There are 2 ways to do that.
Create a dockerfile in the directory that describes the image and then run:
docker build image_name .
Modify the container from the inside and then commit the changes to the image:
docker commit -m "commit message" -a "author" container_id username/imagename:tag
How do you change the container? You start it and then execute commands you like. To run a command inside the container namespace you run the following docker command:
docker exec -ti container_name "command.sh"
And while your container is running, you can do that multiple times: install necessary components, copy files around, etc. In general, you don’t want to build your project into the container, you just map the filesystem on the host to a directory inside the docker container. Then you don’t have to worry about changing the build process for your project and you can run it in the same fashion on your host or in the docker container. But if you build the project in the container, map or commit your local Maven repository, npm modules or that of any dependency management tool you use: if will avoid downloading the internet again and again and make building the project so much faster.
We are now in a position to create and run a container. Let’s stick a full copy of your workstation into a single container. Actually no, instead we’ll be smart and isolate various components from each other.
Consider the following typical project: a web application consists of one or more backend services and a database. While it is possible to fit all that into a single container, that is clearly not a best practice. So we’ll run these parts of the system independently and manage a couple of containers.
Luckily, there’s the a utility that helps us with just that, docker-compose. Docker compose allows you to specify a relationship between several containers in a yml file, and then run all of these containers at once.
The syntax for the docker-compose.yml is pretty straightforward. You specify the container name, the image it should use and the starting command for the container. Additionally you can specify the mapping of the ports to your host machine ports and the mapping of the filesystem, so your docker container can see the files from your host system.
version: "2" services: web: container_name: "web" image: java:8 # image name command: java -jar /app/app.jar # command to run ports: # map ports to the host - "4567:4567" volumes: # map filesystem to the host - ./myapp.jar:/app/app.jar mongo: # container name image: mongo # image name
The best part of all this is that you can link the containers together. In the example above, we link the web container to the mongo container, so our web application will be able to find the database. And docker-compose takes care of providing the port mappings between the linked containers.
Let’s chat more about port mappings. You most likely won’t be able to access your web application on your host machine using localhost as the hostname. This is because your container is running in the docker-machine, instead of your host machine.
One way to overcome that is to use the following command to figure out the IP of your docker-machine:
docker-machine ip default
Alternatively you can use the forward2docker utility, that our colleague Sergei created, to monitor your docker-machine usage and automatically forward the ports to the localhost. This way, you can use your running application in a docker container as if it were available on localhost.
Useful Docker Commands
Let’s go over the most useful docker one line commands that you might need along the way. This section somewhat recaps the commands we saw above, but repetition is the best way to remember something, so stay with us.
Download an image, and all its parents, from the registry:
docker pull image_name
Run a shell command inside a freshly created and started container:
docker run -ti --name container_name image_name /command
Start and stop a container, duh!
docker start container_name docker stop container_name
Run a command inside a container from the image and remove the container when command is done.
docker run --rm -ti image_name /command
Run Tomcat on Java 8 with a custom javaagent attached. This command illustrates mapping the volumes on your host system into the docker container filesystem, see the
-v flag part.
docker run -it --rm -p 8080:8080 -v /host/path/to/your/agent/agent.jar:/agent.jar -e JAVA_OPTS="-javaagent:/agent.jar" tomcat:8.0.29-jre8
Run a shell command in the container:
docker exec -ti container_name "cmd"
To show and follow the log output of the container execute:
docker logs -ft container_name
When finishing a day, you can easily kill all running docker containers:
docker kill $(docker ps -q)
Delete the dangling docker images, the ones that are not tagged properly and are hanging around usually as the result of the intermediate container creation:
docker rmi $(docker images -q -f dangling=true)
Remove all stopped containers, this will actually try to remove all the containers, but will fail to do so with the running ones, so only stopped containers will be gone after that.
docker rm $(docker ps -a -q)
Docker containers ensure applications run quickly and accurately across environments. It’s just one of many tools designed to increase developer productivity. JRebel cancels out the slow redeploys associated with Java development for instant code updates, while maintaining application state. Evaluate JRebel for free today and quantify your time savings.
No comments yet.
Leave a comment