For the uninitiated, Docker is a tool which enables containerization for dependencies for programming. This enables adhering, more closely to the 12 Factor App principles which are designed strategies for synchronizing, more closely, the various environments used in development and mitigating the “works on my machine” problem.
But it is so much more than that. True, Docker has found the best uses as a means to provide for consistent deployments but I see it as much more than this. I see containerization as changing the way we develop applications because it lets us, as developers, do what we love, which is play with new stuff, while still allowing our development environments to be consistent. It normalizes this notion that everything should live together, as a unit, which makes deployment and management so much easier.
Perhaps it does sound grandiose when put this way but I do truly believe it. The ability for me to do LAMP, or LEMP without any of those things installed or the ability to dabble with Go without installing a compiler is huge. I imagine this being the way I want development to go from now on. A project starts and the lead or whomever creates the Dockerfile or the docker-compose file. Developers can then start almost immediately without having to worry about what is installed on their machine and how it might impact what they are working. We can store these files with our source allowing us to take it wherever want to go. I truly find the scenarios enabled by Docker to be amazing and game changing.
The Basics
You download Docker here: https://store.docker.com/search?type=edition&offering=community
Docker is built on the idea of images, effectively these are the templates for the containers which run your apps. The Docker dameaon (installed above) will automatically pull an image if you request one and it does not exist, by default it will pull from Docker Hub. Alternatively, you can pull an image yourself. Here I am pulling the latest version of aspnetcore, which is an image for a container that has the latest .NET Core Runtime installed:
docker pull microsoft/aspnetcore:latest
latest is a tag here to get the newest image available, alternatively you can request a specific version tag such as aspnetcore:2.0.103. By doing this you can pull down a new version of a runtime and see how your code will function in that runtime. A great check before an en masse update.
Once you have the image, you need to create a container. This is done using the run command. You can create many containers from the same image. Containers can be long running (web servers) or throw away (executors). Below I will run our image as a container:
docker run –name core-app microsoft/aspnetcore:latest
If you run the above it will not do much. This is because, while we can think of a container as a VM conceptually, that is not what it is. I like to think that a Container must exist for a purpose, which is contrary to a VM which exists to be used for something. Considered in this light, our above simply starts and then closes. Trick, you can actually see it if you run this command
docker container ls -a
This lists all of the containers on our machine, even those that are not running. So how can we give our container a purpose. For aspnetcore it needs to a web server to run or some sort of process. When dealing with Docker you need to consider the purpose of the container as that is what will drive the general need.
To demonstrate a running container, we are going to go with something a bit simpler, a Go environment. This is where we will write Go code locally and then run it through the container and observe the output. Our container will not need to be long running in this case and exist only long enough to compile and execute our Go code. Let’s get started.
Building a Go Development Environment with Docker
As always, we will be writing code so you will need an editor, so pick your favorite. I tend to use VSCode for most things these days, and it has a Go extension. You will need to disable various popups that complain about not finding Go in the path. It wont be there cause we are not going to install it.
Above we talked about some of the basic commands and only referenced the Dockerfile in passing. But this file is crucial and represents the core building block for an application built on Docker as it lets you take an existing image, customize it and create your own.
Here is the file I have created for my environment
FROM golang:1.8WORKDIR /srcCOPY ./src .RUN go build -o startapp;WORKDIR /appRUN cp /src/startapp .ENTRYPOINT [ “./startapp” ]
- Pull the golang image tagged as 1.8 from a known repository (Docker Hub in this case)
- Change working directory on the image to /src (will create if it does not exist)
- Copy the contents of the host at ./src to the working directory (I have a folder at the root of my project called src where all code files reside)
- Run the command go build -o startapp – this will run the Go compiler and output an executable called startapp
- Change working directory to /app (create if it does not exist)
- Run the copy command to move the created executable to /app
- Set container entrypoint as the startapp executable in the working directory
In effect, this copies our local code into the image, runs a command, and copies that output of that command to a directory. Setting entrypoint tells Docker what it should call when the container is started. You remember how above our run command just exited? That is because we never told it what to do, here we do.
Here is a basic Go Hello World program, I have stored this at /src/start.go
package mainimport “fmt”func main() {fmt.Printf(“Hello World”);}
docker build -t my-app .
This command will directly invoke the <em>Dockerfile</em> in the local directory. Per this, Docker will construct our image using the <strong>golang:1.8</strong> as a base. The -t option allows us to tag the image with a custom name. Once things finish up, use this command to see all of the images on your machine.
docker images
If this is the first time you have used Docker you should see two images in this list, one being that with the same name you used above with -t
Ok, so now we have our image we want to run this as a container. To do that, we use the Docker run command. This will also provide us with our output that we want. Here is a shot of my console.
A few things with this run command:
- In Docker, container names must be unique. Since our container will exist SOLELY to run our Go code, we dont want it hanging around, even in a stopped state. The –rm option ensures that the container is removed once we are done
- –name does what you expect and gives our container a name, if this is omitted Docker will provide a name for us, some of which can be quite amusing
- go-app-hello-world is our target image. This is the image we will use as the template
Congratulations, you have run Go on your machine without installing it. Pretty cool eh?
Expanded Conversations
What we have demonstrated here is but a sliver of the scenarios that Containerization (here through Docker) opens up. If go beyond this and consider a general development scenario, we are also locking ourselves to THIS version of Go. That allows me to install whatever I want on my local machine in the way of tools or other applications and SDKs and have no fear of something being used that would otherwise not be available in production. This principle of isolation is something we have long sought to ensure consistent production deployments.
But there is more to it. Using containers allows for better resource use scenarios in the Cloud through Orchestration tools like Kubernetes, MesOS, and Docker Swarm. These tools enabled codified resilient architectures that can be deployed and managed in the Cloud. And with containerization your code becomes portable meaning if you want to move to AWS from Azure you can, or from AWS to Google. It really is amazing. I look forward to sharing more Docker insights with you.
2 thoughts on “Docker: The Barest of Introductions”