Podman Compose
So far, every example has involved a single container. You run one image, do some work, and stop it. But some workflows require multiple containers running at the same time.
When you need compose
Section titled “When you need compose”Suppose you want an RStudio Server container for R analysis and a JupyterLab container for Python work. You could start each one manually with separate podman run commands. You would need to remember the port mappings, volume mounts, and other flags for each container. Stopping them means running podman stop twice.
This gets tedious fast. It is also error prone. A single compose file solves this by defining all your containers in one place.
Other common multi-container setups include:
- A web application plus a PostgreSQL database for storing results
- An analysis container plus a file browser for viewing outputs
- A pipeline runner plus a monitoring dashboard
What is podman-compose
Section titled “What is podman-compose”podman-compose is a command line tool that reads a docker-compose.yml file and manages multiple containers at once. It starts them together, stops them together, and handles the networking between them.
The compose file format is the same one Docker Compose uses. If you find a docker-compose.yml in a project on GitHub, podman-compose can run it without changes. This is a major advantage. You get access to the entire ecosystem of existing compose files.
Installing podman-compose
Section titled “Installing podman-compose”On Ubuntu or Debian:
$ sudo apt install podman-composeAlternatively, install it with pip:
$ pip install podman-composeVerify the installation:
$ podman-compose --versionThe compose file
Section titled “The compose file”A compose file is a YAML file named docker-compose.yml. It defines services. Each service becomes a container.
Here is a compose file for a bioinformatics development environment with RStudio and JupyterLab:
services: rstudio: image: rocker/rstudio:4.4 ports: - "8787:8787" volumes: - ./data:/home/rstudio/data:ro - ./results:/home/rstudio/results - ./scripts:/home/rstudio/scripts environment: - PASSWORD=changeme
jupyter: image: jupyter/scipy-notebook:latest ports: - "8888:8888" volumes: - ./data:/home/jovyan/data:ro - ./results:/home/jovyan/results - ./notebooks:/home/jovyan/notebooksLet’s walk through this file.
services: is the top level key. Everything below it defines the containers you want to run.
rstudio: and jupyter: are service names. You choose these names. They identify each container.
image: specifies which container image to use. This is the same image you would pass to podman run.
ports: maps ports from the host to the container. The format is "host_port:container_port". RStudio listens on 8787 and Jupyter on 8888.
volumes: mounts directories from your host into the container. The syntax matches the -v flag from podman run. Both services share the same data/ and results/ directories.
environment: sets environment variables inside the container. RStudio Server requires a password.
Notice that both containers mount the same data/ and results/ directories. This means your R scripts and Python notebooks can read the same input data and write to the same results folder.
Basic commands
Section titled “Basic commands”Starting services
Section titled “Starting services”Start all services defined in the compose file:
$ podman-compose upThis runs in the foreground. You will see logs from both containers in your terminal.
To start in the background, add the -d flag:
$ podman-compose up -dThe containers start and you get your terminal back. This is the mode you will use most often.
Checking status
Section titled “Checking status”See which services are running:
$ podman-compose psThis shows each container’s name, status, and port mappings.
Viewing logs
Section titled “Viewing logs”See the output from all services:
$ podman-compose logsFollow logs in real time:
$ podman-compose logs -fView logs for a specific service:
$ podman-compose logs jupyterStopping services
Section titled “Stopping services”Stop and remove all containers:
$ podman-compose downThis stops every container defined in the compose file and removes them. Your data in mounted volumes is preserved.
Building from Dockerfiles in compose
Section titled “Building from Dockerfiles in compose”Sometimes you need a custom image that does not exist on a registry. Instead of building it separately, you can tell compose to build it for you.
Suppose you have a Dockerfile for a custom bioinformatics image:
# biotools/DockerfileFROM ubuntu:24.04
RUN apt-get update && \ apt-get install -y samtools bcftools bedtools && \ apt-get clean && \ rm -rf /var/lib/apt/lists/*
WORKDIR /dataReference it in your compose file with build: instead of image::
services: biotools: build: ./biotools volumes: - ./data:/data:ro - ./results:/results
jupyter: image: jupyter/scipy-notebook:latest ports: - "8888:8888" volumes: - ./data:/home/jovyan/data:ro - ./results:/home/jovyan/results - ./notebooks:/home/jovyan/notebooksThe build: ./biotools line tells compose to look for a Dockerfile in the biotools/ directory and build the image before starting the container.
When you run podman-compose up, compose builds the custom image first. Then it starts both services. If the Dockerfile has not changed, compose reuses the cached image on subsequent runs.
You can force a rebuild with:
$ podman-compose up --buildA practical example
Section titled “A practical example”Here is a complete bioinformatics project that uses compose to run an analysis container alongside a results viewer.
Project structure
Section titled “Project structure”variant_project/├── docker-compose.yml├── analysis/│ └── Dockerfile├── data/│ ├── sample.bam│ └── reference.fa├── results/├── scripts/│ └── call_variants.sh└── notebooks/The analysis Dockerfile
Section titled “The analysis Dockerfile”# analysis/DockerfileFROM ubuntu:24.04
RUN apt-get update && \ apt-get install -y \ samtools \ bcftools \ && apt-get clean \ && rm -rf /var/lib/apt/lists/*
WORKDIR /workspaceCMD ["bash"]The compose file
Section titled “The compose file”services: analysis: build: ./analysis volumes: - ./data:/workspace/data:ro - ./results:/workspace/results - ./scripts:/workspace/scripts:ro stdin_open: true tty: true
viewer: image: jupyter/scipy-notebook:latest ports: - "8888:8888" volumes: - ./results:/home/jovyan/results:ro - ./notebooks:/home/jovyan/notebooksThe analysis service builds a custom image with samtools and bcftools. It mounts your data as read only and writes output to the results directory. The stdin_open and tty options keep the container running so you can attach to it.
The viewer service runs JupyterLab. It mounts the results directory as read only. You can open notebooks to visualize and explore the variant calls.
Running the project
Section titled “Running the project”Start both services:
$ cd variant_project$ podman-compose up -dRun a command inside the analysis container:
$ podman-compose exec analysis bash /workspace/scripts/call_variants.shOpen JupyterLab in your browser at http://localhost:8888 to view the results.
When you are finished, tear everything down:
$ podman-compose downAll containers stop and are removed. Your data, results, and notebooks remain on disk because they live in mounted volumes.
Quick reference
Section titled “Quick reference”| Task | Command |
|---|---|
| Start all services | podman-compose up |
| Start in background | podman-compose up -d |
| Stop and remove all services | podman-compose down |
| List running services | podman-compose ps |
| View logs | podman-compose logs |
| Follow logs in real time | podman-compose logs -f |
| View logs for one service | podman-compose logs <service> |
| Run a command in a service | podman-compose exec <service> <command> |
| Rebuild images and start | podman-compose up --build |
| Force rebuild without cache | podman-compose build --no-cache |
Next steps
Section titled “Next steps”With Linux fundamentals and containers covered, the next sections teach R and Python programming.