In part 1 to this series we installed Docker and got our very first container off the ground. In this post, I want to take this a little further and build a new ubuntu image using a dockerfile. A dockerfile is a file that docker can use to automatically modify an image by running commands, copying in files and mounting drives for resistance.
First lets look at a dockerfile. This is our final product but I wanted to first see some of the commands we can use in our dockerfiles. Its also a shameless plug for VisualStudio Code which is awesome and makes your dockerfile look very nice whilst also introducing autocompletion to commands and repository names. Seriously, get Visual Studio Code.
We use the FROM line to specify the base image
We then use RUN to execute commands within the container
The CMD line sets the default command to run when the container starts but this can be overridden later.
The ENV line sets environment variables within the container
WORKDIR sets our working directory.
This is a complete dockerfile and will get the mongoose-os build tools running within a Ubuntu container, should you want that. Mongoose-OS is an operating system for the popular ESP32 microcontroller which is an awesome bit of kit and I might blog about it one day.
Okay, introductions over lets get going. To build the dockerfile we can either predict the commands we are going to need and write a docker file straight away or do this incrementally. I won’t pretend that I predicted the commands in this dockerfile because actually I found that I had to add an extra step before I could get the additional mongoose repository working. What I’ve found effective is to pull the base container, jump right in and get the final product working whilst noting your steps. For this container, I followed the guide on the mongoose-os website but found through GoogleFu that I needed to install software-properties-common before I could add the repository.
Once you have a working container, you could stop it and commit the container to save its modifications but its better practice to build a dockerfile so you can build it again in future with the latest base image. To build the image based on my dockerfile (based on the mongoose-os instructions) open cmd, navigate to the folder with the dockerfile and run
docker build . -t mos
You should see the image being built. and then a quick look with docker images will show our two images!
From this point, its trivial to run our image with docker run -d mos and then use docker ps to see it running. The -d flag tells docker to run the container as a deamon so it keeps running and does not immediately close.
We can interact with our running container with docker exec -it <image id> bash
The container looks to work fine but the mongoose-os web application on port 1992 is not exposed so we cannot access it from our Windows 10 host. To fix this lets tear down the container by stopping it and removing it.
First we can view our containers with docker ps and then stop it with docker stop <image id>. Remember, you don’t need the full id just enough characters for the docker client to identify the specific container.
As we can see above, the container can be referenced as just “41”. Once we stop it no longer shows up with docker ps because it isn’t running but we can still see it with docker ps -a. It is in this state we can commit our image if we wanted to. Instead we will delete the container with docker rm and then finally confirm its deletion with docker ps -a again. This whole process can be shortened by calling rm against the running container with the -f flag and removing the need for us to stop the container.
With this done, we can spawn our new container but this time with an extra flag: -p 1992:1992 which instructs docker to proxy connections to our PC on port 1992 to the container on port 1992. This means that you should be able to go to localhost:1992 and view the mongoose-os web gui. We can also verify this has worked with the netstat -an command, filtered here with the find utility.
Again we can see this container running with docker ps and then interact with it with docker exec -it <image id> sh
And we are inside the container, running as root within the /root directory (as specified in the dockerfile!).
TOP TIP: I have never really been inclined to find out the difference between sh and bash, other than knowing that bash is the born again shell. Well when you are interacting with containers, sh gives you the nasty raw shell you see in my screenshots. I figured this was a side effect of the container but actually if you change the command from sh to bash you get all the tab completion and history scrolling that you are used to on a linux os.
…So, don’t use sh use bash
Dockerfile on github here.