Learning Docker
The following blog post is just a collection of fairly random efforts that I put in when first encountering docker, and then later diving in with serious development effort.
So I apologize in advance for the “Stream of consciousness” method of writing here.
First step: install docker on MacOS. Open up a terminal zsh, bash or whatever. Then type:
1 | brew cask install docker |
If you need help installing brew see install brew on MacOs.
Next step: fire up this docker tutorial in a self contained container.
1 | docker run -dp 80:80 docker/getting-started |
The tutorial is now running here
What is a container?
Now that you’ve run a container, what is a container? Simply put, a container is simply another process on your machine that has been isolated from all other processes on the host machine. That isolation leverages kernel namespaces and cgroups, features that have been in Linux for a long time. Docker has worked to make these capabilities approachable and easy to use.
What is a container image?
When running a container, it uses an isolated filesystem. This custom filesystem is provided by a container image. Since the image contains the container’s filesystem, it must contain everything needed to run an application - all dependencies, configuration, scripts, binaries, etc. The image also contains other configuration for the container, such as environment variables, a default command to run, and other metadata.
We’ll dive deeper into images later on, covering topics such as layering, best practices, and more.
Building the App’s Container Image
This section is regarding a piece in the getting started tutorial where we install a simple todo app in a container image and run it. In order to build the application, we need to use a Dockerfile. A Dockerfile is simply a text-based script of instructions that is used to create a container image. If you’ve created Dockerfiles before, you might see a few flaws in the Dockerfile below. But, don’t worry! We’ll go over them.
Create a file named Dockerfile in the same folder as the file package.json with the following contents.
1 | FROM node:12-alpine |
Please check that the file Dockerfile has no file extension like .txt. Some editors may append this file extension automatically and this would result in an error in the next step.
If you haven’t already done so, open a terminal and go to the app directory with the Dockerfile. Now build the container image using the docker build command.
docker build -t getting-started .
This command used the Dockerfile to build a new container image. You might have noticed that a lot of “layers” were downloaded. This is because we instructed the builder that we wanted to start from the node:12-alpine image. But, since we didn’t have that on our machine, that image needed to be downloaded.
After the image was downloaded, we copied in our application and used yarn to install our application’s dependencies. The CMD directive specifies the default command to run when starting a container from this image.
Finally, the -t flag tags our image. Think of this simply as a human-readable name for the final image. Since we named the image getting-started, we can refer to that image when we run a container.
The . at the end of the docker build command tells that Docker should look for the Dockerfile in the current directory.
Starting an App Container
Now that we have an image, let’s run the application! To do so, we will use the docker run command (remember that from earlier?).
Start your container using the docker run command and specify the name of the image we just created:
docker run -dp 3000:3000 getting-started
Remember the -d and -p flags? We’re running the new container in “detached” mode (in the background) and creating a mapping between the host’s port 3000 to the container’s port 3000. Without the port mapping, we wouldn’t be able to access the application.
After a few seconds, open your web browser to http://localhost:3000. You should see our app!
Empty Todo List
Go ahead and add an item or two and see that it works as you expect. You can mark items as complete and remove items. Your frontend is successfully storing items in the backend! Pretty quick and easy, huh?
At this point, you should have a running todo list manager with a few items, all built by you! Now, let’s make a few changes and learn about managing our containers.
If you take a quick look at the Docker Dashboard, you should see your two containers running now (this tutorial and your freshly launched app container)!
Docker Dashboard with tutorial and app containers running
Tons of interesting and useful Docker commands
1 | docker build -t myProject-repository-api . |
Take look at the layers in a Docker image
1 | docker history myProject-repository-api |
It should produce output like this
1 | IMAGE CREATED CREATED BY SIZE COMMENT |
Attach and Detach
Attach using this command
1 | docker attach <container name or sha> |
Detach without shutting down the container using
ctrl p + ctrl q
A Dockerfile with dotnet core on a linux Alpine image
I attempted to layer the myProject-repository-api onto this dockerfile
1 | ARG VERSION=3.1-alpine3.10 |
It didn’t quite work. What I ended up using was this:
1 | ARG VERSION=3.1-alpine3.10 |
But, the problem I still have with this Dockerfile is that the /p:EnvironmentName=qa
switch in the publish command, doesn’t work. I had to manually attach to
the running container and swap out the appsettings.json file with the appsettings.qa.json file. But I at least was able to open the Oracle port 1521 and
access the myProject database.
Wait a minute I have the anwer!!!
1 | ENTRYPOINT ["dotnet", "myProject-repository-api.dll", "--environment=qa"] |
And there you have it. Learning as I go with this stuff.
Docker Compose
This will run the todo app as two containers. One with the app and the other for the mySql database.
1 | version: "3.7" |
Then execute docker-compose as follows:
1 | docker-compose up -d |
After docker-compose spins it up you can exec into the mySql container and run mySql commands. It’s pretty cool IMHO.
1 | cd /Users/rhartzell/Downloads/app |
–
Running myProject’s API services in Docker
The following is a quick explanation of how to get both the web-api and the repository-api for myProject, up and running in a docker container.
Currently the Docker files for both APIs are built and will execute with --environment=qa
context. If you want to run these APIs in a different environment
context, simply update the last line of the Docker file.
1 | ENTRYPOINT ["dotnet", "myProject-web-api.dll", "--environment=qa"] |
Anyway. Back to the instructions.
Start by pulling latest for myProject
1 | cd myProject |
Then Simply run the docker build commands to build latest
1 | docker build -t myProject-web-api -f DockerfileWebApi . |
Then run them.
The following commands will ensure that both web and repo apis will run in their respective sockets.
1 | docker run --name repoapi -dp 5006:80 myProject-repository-api --network myProject_api_network --net=host |
To make sure the images are running
Notice how the docker ps
command shows which ports are open in the container and which port the container default port is listening on. Oracle uses port 1521.
1 | ➜ myProject git:(master) docker ps |
Now both APIs are running.
Use a browser to see the Swagger UI for the APIs
- Open a browser for web api -> http://localhost:6100/swagger/index.html
- Open a browser for repo api -> http://localhost:5006/swagger/index.html
If you like this blog or find it useful for you, you are welcome to comment on it. You are also welcome to share this blog, so that more people can participate in it. If the images used in the blog infringe your copyright, please contact the author to delete them. Thank you !