Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Build once, run anywhere

Making your research reproducible with Docker

Eindhoven University of Technology

The problem: dependency hell

Every researcher who has tried to install someone else’s scientific software recognises at least one of these:

This has a name: dependency hell. It is not a sign that you are bad at computers. It is the normal condition of scientific software.

The consequences are not just annoying, they are research problems. You cannot reproduce your own results when you are unsure what tools you actually used. You cannot move to newer resources when your setup isn’t portable. And nobody else can validate or build on your work if it only runs on your machine.

“How can software containers help your research?” — a three-minute introduction from the Australian Research Data Commons, shown during the session.

What is a container?

Start from what you actually want. You want to install research software without breaking your system, and without buying another computer. Imagine you could spin up a separate filesystem and operating system inside your current computer, kept isolated from your main system, containing everything the software needs to run.

That is a container. Docker’s own definition:

A container is a standard unit of software that packages up code and all its dependencies so the application runs reliably from one computing environment to another.

The shipping container analogy

Before shipping containers, cargo was mixed, fragile, and chaotic — every load was a bespoke problem. With standard containers, everything is grouped, portable, and predictable, and the crane does not care what is inside.

Software containers do the same thing: you ship a complete working setup to any computer that has Docker installed, and it just works. What that buys you is reproducibility, portability, isolation, and consistency.

Docker is a tool to build and run containers. It is not the only one, but it is the most widely used.

Images and containers are not the same thing

This is the distinction that trips people up first:

The cookie-cutter analogy from the session:

The image is the cutter, a container is a cookie. One cutter makes as many identical cookies as you like. Containers come and go; the image stays the same.

Getting Docker

Two options, and for a first look the first one is enough:

Check it is working:

docker --version

The commands you actually need

Everything below is from the live walkthrough in the session.

Finding your way around. Docker’s built-in help is genuinely good, and worth using before searching online:

docker --help
docker container --help

Getting an image. Images come from Docker Hub unless you say otherwise:

docker image ls
docker image pull hello-world

Running a container. Each of these starts a container from an image, runs one thing, and exits:

docker container run hello-world
docker container run alpine
docker container run alpine cat /etc/os-release
docker container run alpine echo 'Hello World'

That third command is worth pausing on — cat /etc/os-release reports the operating system inside the container, which is Alpine Linux, whatever your own machine happens to be running.

Going inside. The -it flags give you an interactive shell in the container:

docker container run -it alpine sh

Try ls and whoami once you are in. You are root, in a filesystem that is not yours, and nothing you do there touches your own machine.

Seeing what is running, and cleaning up:

docker container ls          # running containers
docker container ls --all    # including stopped ones
docker image ls
docker image rm <imageid>

Writing a Dockerfile

A Dockerfile is a text file with the instructions to build an image. The shape of one:

FROM python:3.9
COPY . /app
WORKDIR /app
RUN pip install -r requirements.txt
CMD ["python", "app.py"]

Each line is a step: start from an existing image, copy your code in, set the working directory, install what you need, and define what runs by default.

Exercise 1: hello-docker

The smallest complete example. Two files.

app.py:

print("Hello from inside a Docker container!")

Dockerfile:

# Use official Python image as base
FROM python:3.10-slim

# Set working directory in container
WORKDIR /app

# Copy script into container
COPY app.py .

# Set the default command to run
CMD ["python", "app.py"]

Build the image, then run a container from it:

docker build -t hello-docker .
docker run hello-docker

Output:

Hello from inside a Docker container!

Note what you did not do: install Python. The python:3.10-slim base image brought its own, at a version you named and can name again next year.

Exercise 2: getting results back out

A container is disposable — when it exits, anything it wrote inside is gone. That is a problem the moment your code produces output you care about. The answer is a volume: a directory on your machine mounted into the container.

writer.py:

with open("/data/output.txt", "w") as f:
    f.write("Saved from inside the container!\n")

print("File written to /data/output.txt")

Dockerfile:

FROM python:3.10-slim

WORKDIR /app

COPY writer.py .

CMD ["python", "writer.py"]

Build it, then run it with your local data/ folder mounted at /data inside the container:

docker build -t volume-demo .
docker run -v $(pwd)/data:/data volume-demo

The terminal reports File written to /data/output.txt, and — this is the point — output.txt is now in your own data/ folder on your own machine, containing Saved from inside the container!.

The general form is -v <host path>:<container path>. The same mechanism works in reverse: mount your code into a container so you can edit it normally and rerun without rebuilding the image each time.

Networking: a Jupyter server in one command

By default containers are isolated from the network. Publishing a port with -p connects a port inside the container to one on your machine:

docker run -p 8888:8888 -e JUPYTER_TOKEN='mypassword' jupyter/base-notebook

Then open http://localhost:8888/?token=mypassword. That is a complete, working Jupyter environment that you did not install and cannot break, and deleting it afterwards leaves nothing behind.

Why this is worth the effort

Pulling the session’s wrap-up together — what containers give a researcher:

References
  1. Balaban, Ö. (2025). Coding Café - Build Once, Run Anywhere: Making Your Research Reproducible with Docker. 10.5281/ZENODO.15802618