Skip to content

Volumes & Networking

Containers are isolated environments. By default, files created inside a container disappear when the container stops. Your host machine cannot see files inside the container, and the container cannot see files on your host. Volumes solve this problem by creating a bridge between the two.

Imagine you run a DESeq2 analysis inside a container. The script reads count data, produces a results table, and exits. Without a volume, those results vanish the moment the container stops. You would have no way to retrieve them.

Volumes let you mount directories from your host machine into the container. The container can read input files you provide. It can also write output files directly to your host. This is what makes containers practical for real analysis work.

A bind mount maps a directory on your host to a directory inside the container. You create one with the -v flag.

The syntax is:

-v /host/path:/container/path

Everything on the left side of the colon is the path on your machine. Everything on the right side is where it appears inside the container.

Suppose you have a project with this structure:

my_project/
├── data/
│ └── counts.csv
├── results/
└── scripts/
└── analysis.R

You can mount both the data and results directories into a container:

Terminal window
$ podman run --rm \
-v ./data:/data:ro \
-v ./results:/results \
-v ./scripts:/scripts:ro \
my-image Rscript /scripts/analysis.R

This command does several things. It mounts your local data/ directory to /data inside the container. It mounts results/ to /results. It mounts scripts/ to /scripts. Then it runs the R script.

Notice the :ro suffix on the data and scripts mounts. This makes them read-only. The container can read files from these directories but cannot modify or delete them.

Use :ro for input data and scripts. This protects your raw data from accidental modification. It is a simple safeguard that costs nothing.

Terminal window
# The container can read counts.csv but cannot change it
$ podman run --rm \
-v ./data:/data:ro \
my-image cat /data/counts.csv

The results directory is mounted without :ro because the container needs to write output files there.

Here is a concrete workflow. You have RNA-seq count data and want to run differential expression analysis inside a container.

Your project directory:

rnaseq_project/
├── data/
│ ├── counts_matrix.csv
│ └── sample_info.csv
├── results/
└── scripts/
└── deseq2_analysis.R

Run the analysis:

Terminal window
$ podman run --rm \
-v ./data:/data:ro \
-v ./results:/results \
-v ./scripts:/scripts:ro \
bioconductor/bioconductor:3.20 \
Rscript /scripts/deseq2_analysis.R

When the container finishes, check your results directory:

Terminal window
$ ls results/
deseq2_results.csv
volcano_plot.png
ma_plot.png

The output files are on your host machine. The container is gone, but your results persist. This is the key benefit of bind mounts.

You can also mount individual files instead of entire directories. This is useful for configuration files.

Terminal window
$ podman run --rm \
-v ./config.yml:/app/config.yml:ro \
-v ./results:/results \
my-image python /app/run_pipeline.py

Bind mounts tie a container to a specific path on your host. Named volumes are different. Podman manages them, and they are not tied to any particular directory on your machine.

Terminal window
$ podman volume create refgenomes

This creates a volume called refgenomes that Podman stores internally.

Terminal window
$ podman run --rm \
-v refgenomes:/references \
my-image ls /references

The volume name goes on the left side of the colon instead of a host path. Podman recognizes it as a named volume because it is not a file path.

Named volumes are useful when multiple containers need to share the same data. For example, you might download reference genomes once and share them across several analysis containers.

Terminal window
# Download reference genome into the named volume
$ podman run --rm \
-v refgenomes:/references \
my-image wget -O /references/hg38.fa.gz \
https://example.com/hg38.fa.gz
# Use the same volume in a different container
$ podman run --rm \
-v refgenomes:/references:ro \
-v ./data:/data:ro \
-v ./results:/results \
alignment-image bwa mem /references/hg38.fa /data/sample.fastq > results/aligned.sam
Terminal window
# List all volumes
$ podman volume ls
# Inspect a volume
$ podman volume inspect refgenomes
# Remove a volume
$ podman volume rm refgenomes
# Remove all unused volumes
$ podman volume prune

Containers are isolated from the network by default. If a container runs a web server, you cannot reach it from your browser. Port mapping fixes this by forwarding traffic from a port on your host to a port inside the container.

The syntax is:

-p host_port:container_port

RStudio Server is a popular way to use R in a browser. It listens on port 8787 by default.

Terminal window
$ podman run --rm \
-p 8787:8787 \
-v ./project:/home/rstudio/project \
-e PASSWORD=yourpassword \
rocker/rstudio:4.4

Open your browser and go to http://localhost:8787. You will see the RStudio login page. Log in with username rstudio and the password you set.

Your project directory is available inside RStudio because of the volume mount. Any files you create or modify in /home/rstudio/project will appear on your host machine.

Jupyter listens on port 8888 by default.

Terminal window
$ podman run --rm \
-p 8888:8888 \
-v ./notebooks:/home/jovyan/work \
jupyter/scipy-notebook:latest

The container prints a URL with a token to the terminal. Copy that URL and paste it into your browser.

Sometimes port 8787 is already in use on your machine. You can map to a different host port.

Terminal window
$ podman run --rm \
-p 9999:8787 \
-v ./project:/home/rstudio/project \
-e PASSWORD=yourpassword \
rocker/rstudio:4.4

Now RStudio is available at http://localhost:9999 instead. The container still listens on 8787 internally, but your machine forwards port 9999 to it.

You can map more than one port by using the -p flag multiple times.

RStudio Server uses port 8787. Shiny Server uses port 3838. You can expose both.

Terminal window
$ podman run --rm \
-p 8787:8787 \
-p 3838:3838 \
-v ./project:/home/rstudio/project \
-e PASSWORD=yourpassword \
rocker/shiny-verse:4.4

Now you can access RStudio at http://localhost:8787 and your Shiny apps at http://localhost:3838.

Suppose you have a container that runs both a Flask API and a monitoring dashboard.

Terminal window
$ podman run --rm \
-p 5000:5000 \
-p 8050:8050 \
-v ./data:/data:ro \
my-bioinfo-app

Port 5000 serves the API. Port 8050 serves the dashboard. Both are accessible from your browser.

Task Command
Bind mount a directory -v ./data:/data
Bind mount as read-only -v ./data:/data:ro
Create a named volume podman volume create myvolume
Use a named volume -v myvolume:/path
List volumes podman volume ls
Remove a volume podman volume rm myvolume
Map a single port -p 8787:8787
Map to a different host port -p 9999:8787
Map multiple ports -p 8787:8787 -p 3838:3838

You now know how to share files and expose services between containers and your host machine. In the next section, you will learn how to run multi-container setups with Podman Compose.