Docker and Redis from scratch

Tagged as go, docker, redis
Written on 2020-04-19 14:50:06+02:00

Docker is ubiquitous in many projects and therefore it may be useful to dig into more detail about its inner workings. Arguably those aren't too complicated to build a smallish program that does the essentials in a few hours.

The CodeCrafters challenges focus on exactly this kind of idea, taking an existing tool and rebuilding it from scratch. Since they're currently in Early Access, I've only had the opportunity to try out the Docker and Redis challenges so far, but I thought maybe a few insights from them would be good to share.

Part of the challenge is to run the entrypoint of a container; using Go it's actually fairly easy to run external programs. Using the os/exec package is straightforward, even redirecting I/O is easy enough by looking at the Cmd structure a bit closer and assigning values to the Stdin, Stdout and Stderr fields. Also the exit status can be easily gotten from the error return value by checking for ExitError (only if it was not successful, that is, non-zero):

if err = cmd.Run(); err != nil {
    if exitError, ok := err.(*exec.ExitError); ok {

Interestingly enough the SysProcAttr field exposes some functionality that is a bit more difficult to use in, say, C. While using the syscall package is possible, it's mostly easier to assign a few values in that field instead, using the definition of the SysProcAttr structure itself.

Later on there's also the need to parse some JSON - that's again easily done with the standard library, using encoding/json, in particular Unmarshal to a map[string]interface{} (in case we just want to grab a top-level entry in a JSON object), or to a pointer of a custom class using structure tags like so:

type Foo struct {
     Bars []Bar `json:"bars"`

type Bar struct {
     Baz string `json:"baz"`


foo := Foo{}
if err := json.Unmarshal(body, &foo); err != nil {

for _, bar := range foo.Bars {

The Redis challenge is comparatively more contained to just using standard library tools, the most interesting thing I've noticed was that there's now a concurrency-friendly map implementation called sync.Map, so no external synchronization primitive is needed.

What else helped is the redis-cli tool, though I had to find out for myself that it doesn't interpret the specification very strictly, in fact, just about everything returned from the server response will be printed, even when not valid according to the spec.

Overall the biggest challenge here might be to accurately parse the command input and deal with expiration (I simply chose a lazy approach there, instead of clearing out the map on a timer I suppose - this will of course not be memory-friendly long-term, but for implementing a very simple Redis server it's more than enough to pass all tests).


Unless otherwise credited all material Creative Commons License by Olof-Joachim Frahm