This article is Inspired by a question recently posted in a popular tech forum:
How do I create a standalone Linux AMD64 binary for the Furious scanner?
I don’t want to load Go on a device just to use it.
I can only speculate on this person’s objection to loading the Go tools on their device. However, it is a laudable ambition (in my humble opinion) to avoid cluttering systems with software that will only be used once or twice, so I shall endeavor to reward our friend with a more optimal solution to their dilemma.
Furious is a fast, lightweight, portable network scanner from developer Liam Galvin and released under the GPL-3.0 license. Furious is capable of performing IP and port scanning with SYN (stealth) scanning and device manufacturer identification. You can check out the project repository on GitHub and if you like it, maybe show some love by giving it a star.
There are no pre-compiled binaries for Furious released so in order to use it, we need to compile the code from source and we will demonstrate one way to do this later on in this article.
But this article is not really about Furious or how to compile it. The question and the solution I propose for it, perfectly illustrates one of the neat use cases for Docker containers that may not be immediately obvious to many people.
We can use use disposable Docker containers to run ad hoc or scheduled tasks such as building software or taking backups. At our command, a Docker container will be conjured into existence, complete our instructions and exit, ceasing to be, once it has done our bidding.
Docker What?
For the benefit of the uninitiated, let’s take a quick detour to give the TL;DR on what docker containers are all about. In simple terms, a Docker container is a lightweight and portable way to run an application or process in a self-contained environment. Containers are often introduced as ‘like lightweight Virtual Machines’ but the analogy holds up better for system container technologies, such as LXC, than it does for Docker. Containers and Virtual Machines are ‘same same but different.’
Docker was conceived as an ‘application container’ technology. A docker container encapsulates an application and all of its dependencies in an environment that is isolated from the host operating system. This gives the benefit that the application can run on any system that can run Docker and will not behave differently because of different Software versions being present.
In terms of our problem, there are several features of Docker containers that are helpful:
- They are isolated from the host OS
- They are intended to run a single process or service per container
- They are ‘Ephemeral’ – when the container shuts down, it and everything in it is destroyed
Ephemeral What? I Lose My Data When It Shuts Down?
Au contraire my friend! It’s all OK, just don’t store data you care about inside the container. Docker provides Docker volumes to mount data persistently into your container. Your container can also connect to and work with data in other locations such as file shares, object storage or databases.
The ephemeral nature of Docker containers is actually an extremely useful feature and is one of things that made Docker a killer app when it burst onto the tech scene. In the cloud space, applications can be elastically and horizontally scaled in seconds by invoking thousands of containers into existence and then scale back equally as easily, destroying the containers when demand for the app subsides. This is possible because of the ephemeral nature of Docker containers and the separation of data from the application.
Can We Compile Furious Now or What?
I’m glad you asked. Yes we can, thank you for indulging me.
So we will need a system with Docker installed. I used a Debian Bookworm server instance with Docker installed (I use the helper script from Get Docker for Linux installation) but the same steps should work anywhere Docker runs.
We are going use the official Golang image from Docker Hub as our container image (which is version 1.22 at the time of writing)
As an optional step, you can pull the image before we execute our Docker run command:
docker pull golang:1.22
This just means that we have our Golang image ready to go on our device. If we don’t do this, Docker will just go and fetch the correct image the first time we execute our run command if it doesn’t have a local copy.
Now let’s get to the good stuff! We’ll use Docker to create a disposable container to compile Furious from the source code in GitHub.
The command to do this is:
docker run -it --rm -v "$PWD":/build -w / golang:1.22 sh -c "apt update && apt install libpcap-dev -y; git clone https://github.com/liamg/furious.git; cd ./furious; go build -v; cp ./furious /build/"
So let’s break this command down:
docker run
is the command to create a new container-it --rm
flags tell Docker to run the commands in an interactive terminal until the container exits. The –rm flag also tells Docker to remove the container when it’s done.-v "$PWD":/build
mounts your current working directory ($PWD
) as a volume at/build
inside the container. This allows us to share files between our host machine and the container-w /
sets the working directory within the container to the root directory/
golang:1.22
is the Docker image we’ll be using, specifically the Go 1.22 image.sh -c "apt update && apt install libpcap-dev -y; git clone https://github.com/liamg/furious.git; cd ./furious; go build -v; cp ./furious /build/"
is the command that will run inside the container.
Breaking down the sh -c command running in the container further:
- Update the package list and installs libpcap-dev
- Clones the Furious git repository from GitHub
- Changes to the furious directory and compiles the software
- Copies the compiled Furious binary to the /build directory which is mounted to the working directory on the host
If you have followed the steps correctly, you should now have a compiled binary of the latest version of Furious in the working directory where you ran the docker run command.
If you want to cross-compile Furious for Windows or Mac or for a different processor architecture, you can do so by adding the GOOS or GOARCH environment variables into the run command eg.
-e GOOS=windows
to compile for Microsoft Windows-e GOARCH=arm64
to compile for ARM CPU architecture
For those running the command on Windows or Mac, remember that the Go Docker container is running Debian Linux so the command will create a Linux binary by default. If you want a Furious executable that you can run on your Windows or Mac host device, you will have to specify the correct OS and/or CPU architecture accordingly by adding the above environment variables to your docker run command.
Conclusion – Now What?
So if you have made it so far to the end of the post (and possibly even gone so far as to try out compiling Furious following the directions) then hopefully you will have gained some insight into the usefulness of disposable Docker containers to execute specific tasks or processes. This technique can be applied to perform on-demand, automated or scheduled tasks.
Here are is a summary of a few situations where we can take advantage of the disposable Docker containers:
- Building or compiling software.
- Taking backups, copying or syncing files (Top Tip: Use the
--volumes-from
flag in your docker run command to make volumes belonging to an existing container available to your disposable backup container.) - We could create a Docker image of Furious and invoke a container on a schedule to run network scans
- Enterprise example – when you log into GMail, your web client is running in an ephemeral container that connects to your data.
- Enterprise example – Email security systems which open and scan email attachments in a ‘detonation chamber’ before delivery would likely use a disposable container as an isolated environment.
Of course there are often many different solutions to a given problem but now, thanks to Docker, we have one more tool in our repertoire.