Contents

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:

1
2
	file, err := os.OpenFile(file_name, os.O_CREATE|os.O_WRONLY, 0644)
  // handle err

and if there is no error, I can write strings using file.WriteString:

1
2
3
	if _, err := file.WriteString(line + "\n"); err != nil {
   // code
  }

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10

	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		text := scanner.Text()
		f.add(text)

		if err := scanner.Err(); err != nil {
			log.Fatal("Error while scanning next line")
		}
	}
  • 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)
  • after stripping symbols:
    • command go build -ldflags="-w -s"; stat -c %s fme
    • size: 1.3MB (1273856)
  • after disableing function inlining:
    • command: go build -ldflags="-w -s" -gcflags=all=-l; stat -c %s fme
    • size: 1.2MB 1253376

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 finally docker 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
  • 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/data sets the environment variable that the container will be able to read and will write teh file there
  • note: -v·"$PWD":/root/data bind-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.

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:

./first_action.png

So when it completed… ./completed.png

…I checked the dockerhub: ./dockerhub_pushed.png

…and was able to run latest version: ./latest_from_dockerhub.png

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:

./comparison.jpg

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!

Reference