Docker exercise

No amount of Udemy courses is satisfactory unless you start to solve your problems. So yesterday I created myself a problem:
Write a commandline application, dockerize it, use it, push it to dockerhub.
It seems like a very humble endeavour, and I had a lot of fun anyway.
Golang app
I’ve learned that in order to write to a file I can use os.Open call which
takes flags and (hex) permissions:
|
|
and if there is no error, I can write strings using file.WriteString:
|
|
Then I dicovered a very peculiar way of using a Scanner - a way that is very close and dear to my heart: a very clear separation between changing a state and getting data.
Have a look:
|
|
- scanner.Scan() scans the next token; it does not return the token, it may
however set the error as the result. The error can be inspected in a call to
scanner.Err() - scanner.Text() returns the last token and does not proceed, i.e. does not chaneg the state of the scanner
This pattern is known as Command-query separation and I really appreciate its elegance.
Back to the app: it basically allows you to enter your “goals” line by line
and after you type “q” as the single character on a line, it would save all your
entered goals to betterme.txt file in a current working directory.
As a bonus, it keeps previous lines from betterme.txt file 😄 so that
your previously entered goals aren’t forgotten.
Dockerization
My Dockerfile is stolen from the internet. I’ve just
- set golang version to exaclty the version I use in my go.mod
- set the binary name to be
fme
My Dockerfile in layered - and my resulting image size is 9.29MB.
By the way, the binary itself, when built on my laptop, can be much smaller:
- normal build command:
- command:
go build . - size: 2.0 MB (1958297)
- command:
- after stripping symbols:
- command
go build -ldflags="-w -s"; stat -c %s fme - size: 1.3MB (1273856)
- command
- after disableing function inlining:
- command:
go build -ldflags="-w -s" -gcflags=all=-l; stat -c %s fme - size: 1.2MB 1253376
- command:
Pushing the image
In order to push the image to dockerhub, I needed to
- access my “account”
- create a “repository” (which I called
fme) - set a tag on my image with this repository “prefix”:
docker tag fme:v1.0.0 kamchyd/fme:v1.0.0 - login from commandline (
docker login -u kamchyc), enter password and finallydocker push kamchyd/fme:v1.0.0
So, this was easy (see 00ef0fe).
Somehow make it write to host
I know, it is like reaching to your left ear with your right hand but… I think I don’t want my container to store the file in its writable layer. I want it to modify a file in my current directory.
Since the application binary is located and starts in /root directory, and becasue it writes a file in a current directory, I could not bindmount current directory on my host to a /root inside teh container (becasue then the binary was not found - my bindmount overrode it).
So I came up with following strategy:
- change the image
- build it with /root/data created, as v1.0.1 (
docker build -t fme:v1.0.1 .) - tag:
docker tag fme:v1.0.1 kamchyd/fme:v1.0.1 - push: docker push kamchyd/fme:v1.0.1
- see b2a771c2cd
- build it with /root/data created, as v1.0.1 (
- make the code read the directory from an environment variable,
FMEDIR- which is much nicer than assuming filesystem layout
- and will allow me to still run the code locally
- see 061b998062
- bindmount current directory on the host to the other directory, e.g. /root/data
I create and run the container (I don’t even care about its name as I don’t want to keep it -
I no longer want the state inside the container) with the following command:
docker run -e FMEDIR=/root/data -it --rm -v "$PWD":/root/data fme:v1.0.1
- note:
-e FMEDIR=/root/datasets the environment variable that the container will be able to read and will write teh file there - note:
-v·"$PWD":/root/databind-mounts my current working dir to the container’s /root/data - if the app reads FMEDIR it finds /root/data which is bindmounted to my current dir hence it is writing to my local dir.
How to run
I created a start.sh script that runs the image (with the above command).
Youcan see it here (commit d6f1154)
This is unsafe
Yes it is. Remember, this is only the exericse.
- your containers in general should not have access to your host filesystem
- you should monitor the images (especially base image) for vulnerabilities
- you should read docker security best practices or dig deeper and read docer security article
GitHub action
Now I’d like to set up github action that updates the image each time I push/merge to master.
I found all I need on docker page which explains what needs to be done.
I created proper secrets and pushed the workflow. What would happen? Nothing happened.
Code update
In order for GitHub to pick up my changes I needed to change something in my code - so I changed some static strings and pushed to GH. And still nothing happened.
Problem
When you copy stuff from internet, look carefuly.
The example I used from docker page had a “correct” branch name. I had the “incorrect” 😄
I also noticed nad image pushed to dockerhub will have kamchy/fme:latest tag.
Solution
I needed to modify the .yml with my push branch name (main -> master) and finally I saw this:

So when it completed…

…I checked the dockerhub:

…and was able to run latest version:

Check
I don’t have kamchy/fme:latest locally. This image was build y ci on github and pushed to dockerhub.
Let’s run my app using the start.sh script (which points to kamchy/fme:v1.0.1) and compare it with
the app from new latest image. If there is a difference in static strings, I’m good:

Final words
I love my playground. It gives me so huge dopamine rush I can barely write!
- my first golang app dockerized
- my first project with github actions
- my first app (and correspondign blog article) written using helix editor
So, now it’s time for a glass of water and for a long walk.
See you!