# Container Camp — full corpus Each page below begins with its canonical URL followed by its original Markdown, OKF frontmatter included. Relative links have been rewritten to absolute URLs. ---8<--- https://unm-carc.github.io/container-camp/getting-started/overview/ --- title: "Workshop overview" description: "What CyVerse Container Camp covered in its Basics and Cloud Native tracks, the workshop learning objectives, and CyVerse's NSF funding." type: Guide tags: - Containers - Workshop - Overview generated: by: "claude/opus-5" at: "2026-09-11T00:00:00Z" sources: - id: cyverse-container-camp resource: "https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/index.md" title: "CyVerse Container Camp: docs/index.md" author: "team:cyverse" last_modified: "2023-08-04T13:19:03-07:00" --- # Workshop overview [{width="400"}](https://unm-carc.github.io/cyverse/){target=_blank}  Welcome to our documentation on containers and orchestration in academic research and education. CyVerse ran Container Camp as two paired workshops. In 2023, **Container Camp Basics** met March 6-8 ([agenda](https://unm-carc.github.io/container-camp/getting-started/schedule-basics/)) and the **Cloud Native Camp** met August 16-18 ([agenda](https://unm-carc.github.io/container-camp/getting-started/schedule/)). The schedules are kept as a record of those offerings; the lessons below can be worked through on your own at any time. ## Basic Container Camp ### Docker containers for scientific research - [Introduction to Docker](https://unm-carc.github.io/container-camp/docker/intro/) - [Finding the right container](https://unm-carc.github.io/container-camp/docker/registry/) - [Building Docker images](https://unm-carc.github.io/container-camp/docker/build/) ### Containers on high performance and high throughput computing - [Introduction to Apptainer/Singularity](https://unm-carc.github.io/container-camp/singularity/intro/) - [Building Apptainer/Singularity images](https://unm-carc.github.io/container-camp/singularity/advanced/) - [Apptainer/Singularity on HPC](https://unm-carc.github.io/container-camp/singularity/hpc/) ## Cloud Native Camp ### Orchestration - [Container orchestration with Kubernetes](https://unm-carc.github.io/container-camp/orchestration/kubernetes/) ### Infrastructure as Code - [Infrastructure as Code with HashiCorp Terraform](https://unm-carc.github.io/container-camp/orchestration/terraform/) - [CyVerse's Cloud Automation & Continuous Analysis Orchestration (CACAO)](https://unm-carc.github.io/container-camp/orchestration/cacao/) !!! success "Learning Objectives" After completing these two paired workshops, you should be able to: * Explain why containers and orchestration are used in research computing * Create your own containers and deploy your own orchestrated frameworks * Understand how and when to use containers and IaC in your daily work You will also leave with - Understanding of how to use the most powerful public research computing infrastructure in the world via [ACCESS-CI](https://access-ci.org){target=_blank}. - The ability to launch and manage distributed resources using IaC templates on commercial (AWS, GCloud, Azure) or public research (OpenStack) clouds. - Insight into commercial cloud services, their costs, and how to best utilize them for scientific research. --- **Funding and Citations:** CyVerse is generously funded by the National Science Foundation [{width="25"}](https://nsf.gov){target=_blank}. Here are our award numbers: - [{target=_blank}](https://www.nsf.gov/awardsearch/showAward?AWD_ID=0735191) - [{target=_blank}](https://www.nsf.gov/awardsearch/showAward?AWD_ID=1265383) - [{target=_blank}](https://www.nsf.gov/awardsearch/showAward?AWD_ID=1743442) For any utilization of our resources, please adhere to the [CyVerse citation policy](https://cyverse.org/policies/cite-cyverse){target=_blank}.
Adapted from [CyVerse Container Camp](https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/index.md){target=_blank} (last source update 2023-08-04), CC BY 4.0. Spotted a problem? [Open an issue](https://github.com/UNM-CARC/container-camp/issues){target=_blank}.
---8<--- https://unm-carc.github.io/container-camp/getting-started/code-of-conduct/ --- title: "Code of Conduct" description: "The anti-harassment code of conduct that applied to all Container Camp attendees, speakers, staff, and volunteers." type: Policy tags: - Code of Conduct - Workshop generated: by: "claude/opus-5" at: "2026-09-11T00:00:00Z" sources: - id: cyverse-container-camp resource: "https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/getting_started/code_conduct.md" title: "CyVerse Container Camp: docs/getting_started/code_conduct.md" author: "team:cyverse" last_modified: "2022-04-20T19:25:56Z" --- # Code of Conduct All attendees, speakers, staff and volunteers at Container Camp are required to follow our code of conduct. CyVerse expects and appreciates cooperation from all participants to help ensure a safe, collaborative environment for everyone. Harrassment by any individual will not be tolerated and may result in the individual being removed from the Camp. Harassment includes: offensive verbal comments related to gender, gender identity and expression, age, sexual orientation, disability, physical appearance, body size, race, ethnicity, religion, technology choices, sexual images in public spaces, deliberate intimidation, stalking, following, harassing photography or recording, sustained disruption of talks or other events, inappropriate physical contact, and unwelcome sexual attention. Participants who are asked to stop any harassing behavior are expected to comply immediately. Workshop staff are also subject to the anti-harassment policy. In particular, staff should not use sexualised images, activities, or other material. If a participant engages in harassing behavior, the workshop organisers may take any action they deem appropriate, including warning the offender or expulsion from the workshop with no refund. If you are being harassed, or notice that someone else is being harassed, or have any other concerns, please contact a member of the workshop staff immediately. Staff can be identified as they'll be wearing badges or nametags. Workshop staff will be happy to help participants contact local law enforcement, provide escorts, or otherwise assist those experiencing harassment to feel safe for the duration of the workshop. We value your attendance. We expect participants to follow these rules at conference and workshop venues and conference-related social events. See http://www.ashedryden.com/blog/codes-of-conduct-101-faq for more information on codes of conduct.Adapted from [CyVerse Container Camp](https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/getting_started/code_conduct.md){target=_blank} (last source update 2022-04-20), CC BY 4.0. Spotted a problem? [Open an issue](https://github.com/UNM-CARC/container-camp/issues){target=_blank}.
---8<--- https://unm-carc.github.io/container-camp/getting-started/setup/ --- title: "Pre-camp setup" description: "Accounts to create before starting the lessons: GitHub and Docker Hub, plus optional CyVerse and NSF ACCESS accounts." type: Guide tags: - Setup - Accounts generated: by: "claude/opus-5" at: "2026-09-11T00:00:00Z" sources: - id: cyverse-container-camp resource: "https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/getting_started/setup.md" title: "CyVerse Container Camp: docs/getting_started/setup.md" author: "team:cyverse" last_modified: "2023-07-28T08:51:15-07:00" --- # Pre-camp setup ## :material-clipboard-check-multiple: Prerequisites Before attending Container Camp (Basics) please do the following: [Create a GitHub Account](https://github.com){target=_blank} [Create a Docker Hub Account](https://hub.docker.com){target=_blank} **Optional** [Create a CyVerse Account](https://user.cyverse.org){target=_blank} Before attending Cloud Native Camp please do the following: :fontawesome-regular-hand-point-up: **(all of the above)** [Create an ACCESS-CI profile](https://access-ci.org/){target=_blank}Adapted from [CyVerse Container Camp](https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/getting_started/setup.md){target=_blank} (last source update 2023-07-28), CC BY 4.0. Spotted a problem? [Open an issue](https://github.com/UNM-CARC/container-camp/issues){target=_blank}.
---8<--- https://unm-carc.github.io/container-camp/getting-started/schedule-basics/ --- title: "Container Camp Basics schedule (March 2023)" description: "Day-by-day agenda of the March 6-8, 2023 Container Camp Basics: Docker, Docker Compose, and Apptainer/Singularity." type: Schedule tags: - Schedule - Docker - Apptainer stale_after: "2023-03-09T00:00:00Z" generated: by: "claude/opus-5" at: "2026-09-11T00:00:00Z" sources: - id: cyverse-container-camp resource: "https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/getting_started/schedule_basics.md" title: "CyVerse Container Camp: docs/getting_started/schedule_basics.md" author: "team:cyverse" last_modified: "2023-07-27T11:00:45-07:00" --- # Container Camp Basics schedule (March 2023) This page records the agenda of the 2023 Container Camp Basics, held March 6-8, 2023 (times are Mountain Standard Time, Arizona). The session times and instructor assignments are historical; the linked lessons remain available to work through on your own. ## Day 1 - Introduction to Docker ??? note "Content" - Introduction to Docker and its uses in reproducible science - Launching development environments on CodeSpaces for container testing - Using Docker on the commandline. ??? success "Goals" - Introduction to containers & [where to find them](https://unm-carc.github.io/container-camp/docker/registry/) - Command line containers with CodeSpaces (optional: run locally) - Find and use official Docker images ???+ example "Activities" | Time (MST/AZ)| Activity | Instructor | Outcome | |-----------|----------|------------|-------| | 09:00 | Welcome | All | | 09:05 | Overview of Website | Michele | [Code of Conduct](https://unm-carc.github.io/container-camp/getting-started/code-of-conduct/) | | 09:10 | Why use Containers? | Carlos | What containers are used for in science | | 09:25 | [Start CodeSpace :material-microsoft-visual-studio-code: :material-github:](https://unm-carc.github.io/container-camp/cloud/codespaces/) | Tyson | Using Dev Environments to create containers| | 09:30 | [:material-docker: Docker Commands and Execution](https://unm-carc.github.io/container-camp/docker/intro/#fundamental-docker-commands) | Carlos | Basic command line use of Docker | | 09:55 | Break | | | 10:00 | [:material-docker: Docker Commands and Execution](https://unm-carc.github.io/container-camp/docker/intro/#fundamental-docker-commands) | Carlos & Tyson | Basic command line use of Docker | | 10:55 | Break | | | 11:00 | [:material-docker: Managing Docker and Data](https://unm-carc.github.io/container-camp/docker/intro/#managing-data-in-docker) | Michele | Volumes and Interactive Use inside containers | | 11:55 | Break | | | | 12:00 | [:material-docker: Managing Docker and Data](https://unm-carc.github.io/container-camp/docker/intro/) | Michele & Tyson | | | 12:55 | Conclude for the day | All | push changes to your GitHub | ??? warning "Optional Homework" - Test other Docker container images on CodeSpaces or locally ## Day 2 - Building Docker Containers ??? note "Content" - Use GitHub to browse for public Dockerfiles - Build Dockerfiles and push them to public registry - Use Version Control to set up automated container builds with GitHub Actions ??? success "Goals" - Introduction to what Dockerfiles are and what you use them for - Start thinking about how to modify them for your own applications ???+ example "Activities" | Time (MST/AZ)| Activity | Instructor | Notes | |-----------|----------|------------|-------| | 09:00 | Welcome back | All | | | 09:05 | Discuss previous day, answer questions | | | | 09:15 | [(re)start Dev Environment :material-microsoft-visual-studio-code: :material-github:](https://unm-carc.github.io/container-camp/cloud/codespaces/) | Tyson | | | 09:20 | [Finding the right container :material-docker:](https://unm-carc.github.io/container-camp/docker/registry/) | Tyson | | | 09:55 | Break | | | 10:00 | [Building Docker Images :material-docker:](https://unm-carc.github.io/container-camp/docker/build/) | Carlos | | | 10:55 | Break | | | 11:00 | [Using Docker Compose :material-docker:](https://unm-carc.github.io/container-camp/docker/compose/) | Michele | | | 11:55 | Break | | | | 12:00 | [Integrating your Containers into CyVerse](https://unm-carc.github.io/cyverse/discovery-environment/create-apps/){target=_blank} | Tyson | | | 12:55 | Conclude | | | ## Day 3 - Singularity, Orchestration, and Containers on the HPC ??? note "Content" - Introduction to Singularity - Introduction to Kubernetes ??? success "Goals" - Being able to execute containers on the HPC - Create a small Kubernetes cluster ???+ example "Activities" | Time (MST/AZ)| Activity | Instructor | Notes | |-----------|----------|------------|-------| | 09:00 | Welcome back | All | | | 09:05 | Discuss previous day, answer questions | | | | 09:15 | [(re(re))start Dev Environment :material-microsoft-visual-studio-code: :material-github:](https://unm-carc.github.io/container-camp/cloud/codespaces/) | | | | 09:20 | [Introduction to Singularity](https://unm-carc.github.io/container-camp/singularity/intro/) | | | 09:30 | [Obtaining Singularity](https://unm-carc.github.io/container-camp/singularity/intro/#installing-apptainer-or-singularityce) and [the Singularity CLI](https://unm-carc.github.io/container-camp/singularity/intro/#apptainer-and-singularity-cli) | | | 09:55 | Break | | | 10:00 | [Singularity Commands & Interacting with Singularity Images](https://unm-carc.github.io/container-camp/singularity/intro/#interact-with-images) | | | 10:55 | Break || | 11:00 | [Singularity Images Continued](https://unm-carc.github.io/container-camp/singularity/advanced/#apptainer-and-singularity-cli-continued)* | | | 11:55 | Break || | 12:00 | [Integrating your Containers into CyVerse](https://unm-carc.github.io/cyverse/discovery-environment/create-apps/){target=_blank}, questions, inquiries | | | 12:55 | Conclude** | | \* If we still have time, we will also discuss using [Singularity on the HPC](https://unm-carc.github.io/container-camp/singularity/hpc/#how-do-hpc-systems-fit-into-the-development-workflow). \*\* For additional questions and inquiries, we will make time for one-on-ones on either Thursday or Friday (flexible schedule).Adapted from [CyVerse Container Camp](https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/getting_started/schedule_basics.md){target=_blank} (last source update 2023-07-27), CC BY 4.0. Spotted a problem? [Open an issue](https://github.com/UNM-CARC/container-camp/issues){target=_blank}.
---8<--- https://unm-carc.github.io/container-camp/getting-started/schedule/ --- title: "Cloud Native Camp schedule (August 2023)" description: "Day-by-day agenda of the August 16-18, 2023 Cloud Native Camp: Kubernetes, Terraform, and CyVerse CACAO." type: Schedule tags: - Schedule - Kubernetes - Terraform - CACAO stale_after: "2023-08-19T00:00:00Z" generated: by: "claude/opus-5" at: "2026-09-11T00:00:00Z" sources: - id: cyverse-container-camp resource: "https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/getting_started/schedule.md" title: "CyVerse Container Camp: docs/getting_started/schedule.md" author: "team:cyverse" last_modified: "2023-08-17T01:01:22Z" --- # Cloud Native Camp schedule (August 2023) This page records the agenda of the 2023 Cloud Native Camp, held August 16-18, 2023 (times are Pacific Daylight Time). The session times and instructor assignments are historical; the linked lessons remain available to work through on your own. ## Day 1 - Container Orchestration !!! abstract "Content" * Introduction to Kubernetes * K8s CLI * Deploying K8s clusters !!! success "Learning Objectives" * Comfort working in the terminal with a remote K8s cluster * Understanding of K8s as it is used to manage containers * Start your own Zero2JupyterHub !!! example "Agenda" | Time ([PDT](https://time.is/PDT){target=_blank}) | Activity | Instructor | Outcome | |-----------|----------|------------|-------| | 09:00 | Welcome | All | | 09:05 | Overview of Website | Tyson | [Code of Conduct](https://unm-carc.github.io/container-camp/getting-started/code-of-conduct/) | | 09:10 | What is Kubernetes? | Tyson | overview of K8s ecosystem | | 09:30 | [:simple-kubernetes: K8s CLI](https://unm-carc.github.io/container-camp/orchestration/kubernetes/#k8s-cli-kubectl) | Tyson | Basic command line use of K8s | | 09:55 | Break | | | 10:00 | [:simple-kubernetes: K8s CLI cont.](https://unm-carc.github.io/container-camp/orchestration/kubernetes/#k8s-cli-kubectl) | Tyson & Michele | Connecting to a K8s cluster | | 10:55 | Break | | | 11:00 | [:material-docker: Deploying K8s clusters](https://unm-carc.github.io/container-camp/orchestration/kubernetes-advanced/) | Michele | Zero2JupyterHub | | 11:55 | Break | | | | 12:00 | [:simple-kubernetes: Managing K8s clusters](https://unm-carc.github.io/container-camp/orchestration/kubernetes-advanced/) | Michele & Tyson | | | 12:55 | Conclude for the day | All | push changes to GitHub | ??? warning "Homework" * Consider what type of hardware & container orchestration you're most interested in and come ready with your own ideas for deploying something with Terraform on Day 2. ## Day 2 - Infrastructure as Code !!! abstract "Content" * Use Terraform to provision hardware and deploy applications & containers * Build Terraform templates !!! success "Learning Objectives" * Understanding of what IaC is and how it fits along with the orchestration of containers and cloud * Ability to launch your own cloud instances on at least one cloud platform (OpenStack) * Ability to provision your instances with Terraform and access them !!! example "Agenda" | Time ([PDT](https://time.is/PDT){target=_blank}) | Activity | Instructor | Outcome | |-----------|----------|------------|-------| | 09:00 | Welcome back | All | | | 09:05 | Discuss previous day, answer questions | | | | 09:20 | [:simple-terraform: What is Terraform](https://unm-carc.github.io/container-camp/orchestration/terraform/) | Edwin & Tyson | | | 09:55 | Break | | | 10:00 | [:simple-terraform: Terraform CLI](https://unm-carc.github.io/container-camp/orchestration/terraform/) | Tyson | | | 10:55 | Break | | | 11:00 | [:simple-terraform: Using a Terraform to simply manage Docker](https://unm-carc.github.io/container-camp/orchestration/terraform-advanced/#using-a-terraform-to-simply-manage-docker) | Edwin & Michele | | | 11:55 | Break | | | | 12:00 | [:simple-terraform: Terraform management continued](https://unm-carc.github.io/container-camp/orchestration/terraform-advanced/) | Edwin & Tyson | | | 12:55 | Conclude | | | ## Day 3 - CACAO !!! abstract "Content" - Introduction to CyVerse CACAO - Writing advanced Terraform templates for CACAO !!! success "Learning Objectives" - Being able to execute containers on the HPC - Create a small Kubernetes cluster !!! example "Agenda" | Time ([PDT](https://time.is/PDT){target=_blank}) | Activity | Instructor | Outcome | |-----------|----------|------------|-------| | 09:00 | Welcome back | All | | | 09:05 | Discuss previous day, answer questions | | | | 09:20 | [Introduction to CACAO](https://unm-carc.github.io/container-camp/orchestration/cacao/) | Edwin | | 09:30 | [Using the CACAO UI](https://unm-carc.github.io/container-camp/orchestration/cacao/) | Edwin | | 09:55 | Break | | | 10:00 | [Overview of CACAO templates](https://unm-carc.github.io/container-camp/orchestration/cacao-terraform/)| Edwin | | 10:55 | Break || | 11:00 | [Creating a CACAO template](https://unm-carc.github.io/container-camp/orchestration/cacao-terraform/) | Edwin | | 11:55 | Break || | 12:00 | Show and Tell - present your use cases | everyone | | 12:55 | Conclude | |Adapted from [CyVerse Container Camp](https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/getting_started/schedule.md){target=_blank} (last source update 2023-08-17), CC BY 4.0. Spotted a problem? [Open an issue](https://github.com/UNM-CARC/container-camp/issues){target=_blank}.
---8<--- https://unm-carc.github.io/container-camp/docker/intro/ --- title: "Introduction to Docker" description: "Run, inspect, and clean up Docker containers from the command line, mount data with volumes, and expose ports for browser-based IDEs." type: Lesson tags: - Docker - Containers - Command Line generated: by: "claude/opus-5" at: "2026-09-11T00:00:00Z" sources: - id: cyverse-container-camp resource: "https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/docker/intro.md" title: "CyVerse Container Camp: docs/docker/intro.md" author: "team:cyverse" last_modified: "2023-08-15T20:54:48-07:00" --- # Introduction to Docker [{ width="400" }](https://hub.docker.com){target=_blank} ### Prerequisites In order to complete these exercises we **STRONGLY** recommend that you set up a personal [:material-github: GitHub](https://github.com){target=_blank} and [:material-docker: DockerHub](https://hub.docker.com){target=_blank} account (account creation for both services is free). There are no specific skills needed for this tutorial beyond elementary command line ability and using a text editor. We are going to be using [:material-github: GitHub CodeSpaces](https://github.com/features/codespaces){target=_blank} for the hands on portion of the workshop, which features [:material-microsoft-visual-studio-code: VS Code](https://code.visualstudio.com/){target=_blank} as a fully enabled development environment with Docker already installed. CodeSpaces is a featured product from GitHub and requires a paid subscription or Academic account for access. Your account will temporarily be integrated with the course GitHub Organization for the next steps in the workshop. Our instructions on starting a new CodeSpace are [here](https://unm-carc.github.io/container-camp/cloud/codespaces/). ??? info "Installing Docker on your personal computer" We are going to be using virtual machines on the cloud for this course, and we will explain why this is a good thing, but there may be a time when you want to run Docker on your own computer. Installing Docker takes a little time but it is reasonably straight forward and it is a one-time setup. Installation instructions from Docker Official Docs for common OS and chip architectures: - [:fontawesome-brands-apple: Mac OS X](https://docs.docker.com/docker-for-mac/){target=_blank} - [:fontawesome-brands-windows: Windows](https://docs.docker.com/docker-for-windows){target=_blank} - [:fontawesome-brands-ubuntu: Ubuntu Linux](https://docs.docker.com/install/linux/docker-ce/ubuntu/){target=_blank} ??? failure "Never used a terminal before?" That is 💯 OK! (This person never used a terminal until after their terminal degree, and now they actually PREFER to work in it for writing code) Don't be afraid or ashamed, but be ready to learn some new skills -- we promise it will be worth your while and even FUN! Before venturing much further, you should review the [Software Carpentry](https://software-carpentry.org/lessons/){target=_blank} lessons on "The Unix Shell" and "Version Control with Git" -- these are great introductory lessons related to the skills we're teaching here. You've given up on ever using a terminal? No problem, Docker can be used from graphic interfaces, like [Docker Desktop](https://www.docker.com/products/docker-desktop/){target=_blank}, or platforms like [Portainer](https://www.portainer.io/){target=_blank}. We suggest you read through their documentation on how to use Docker. ## Fundamental Docker Commands :octicons-terminal-16: Docker commands in the terminal use the prefix `docker`. !!! note For every command listed, the correct execution of the commands through the command line is by using `docker` in front of the command: for example `docker help` or `docker search`. Thus, every :material-docker: = `docker`. ### :material-docker: help Like many other command line applications the most helpful flag is the `help` command which can be used with the Management Commands: ``` $ docker $ docker --help ``` ### :material-docker: search We talk about the concept of [Docker Registries](https://unm-carc.github.io/container-camp/docker/registry/) in the next section, but you can search the public list of registeries by using the `docker search` command to find public containers on the Official [Docker Hub Registry](https://hub.docker.com){target=_blank}: ``` $ docker search ``` ### :material-docker: pull Go to the [Docker Hub](https://hub.docker.com){target=_blank} and type `hello-world` in the search bar at the top of the page. Click on the 'tag' tab to see all the available 'hello-world' images. Click the 'copy' icon at the right to copy the `docker pull` command, or type it into your terminal: ``` $ docker pull hello-world ``` !!! note If you leave off the `:` and the tag name, it will by default pull the `latest` image ``` $ docker pull hello-world Using default tag: latest latest: Pulling from library/hello-world 2db29710123e: Pull complete Digest: sha256:bfea6278a0a267fad2634554f4f0c6f31981eea41c553fdf5a83e95a41d40c38 Status: Downloaded newer image for hello-world:latest docker.io/library/hello-world:latest ``` Now try to list the files in your current working directory: ``` $ ls -l ``` ??? question "Where is the image you just pulled?" Docker saves container images to the Docker directory (where Docker is installed). You won't ever see them in your working directory. Use 'docker images' to see all the images on your computer: ``` $ docker images ``` ??? tip "adding yourself to the Docker group on Linux" Depending on how and where you've installed Docker, you may see a `permission denied` error after running `$ docker run hello-world` command. If you're on Linux, you may need to prefix your Docker commands with `sudo`. Alternatively to run docker command without `sudo`, you need to add your user name (who has root privileges) to the docker "group". Create the docker group: ``` $ sudo groupadd docker ``` Add your user to the docker group:: ``` $ sudo usermod -aG docker $USER ``` Log out or close terminal and log back in and your group membership will be initiated ### :material-docker: run The single most common command that you'll use with Docker is `docker run` ([see official help manual](https://docs.docker.com/engine/reference/commandline/run/){target=_blank} for more details). `docker run` starts a container and executes the default "entrypoint", or any other "command" that follows `run` and any optional flags. ??? tip "What is an *entrypoint*?" An entrypoint is the initial command(s) executed upon starting the Docker container. It is listed in the `Dockerfile` as `ENTRYPOINT` and can take 2 forms: as commands followed by parameters (`ENTRYPOINT command param1 param2`) or as an executable (`ENTRYPOINT [“executable”, “param1”, “param2”]`) ``` $ docker run hello-world:latest ``` In the demo above, you used the `docker pull` command to download the `hello-world:latest` image. What about if you run a container that you haven't downloaded? ``` $ docker run alpine:latest ls -l ``` When you executed the command `docker run alpine:latest`, Docker first looked for the cached image locally, but did not find it, it then ran a `docker pull` behind the scenes to download the `alpine:latest` image and then execute your command. When you ran `docker run alpine:latest`, you provided a command `ls -l`, so Docker started the command specified and you saw the listing of the Alpine file system (not your host system, this was insice the container!). ### :material-docker: images You can now use the `docker images` command to see a list of all the cached images on your system: ``` $ docker images REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE alpine latest c51f86c28340 4 weeks ago 1.109 MB hello-world latest 690ed74de00f 5 months ago 960 B ``` ??? info "Inspecting your containers" To find out more about a Docker images, run `docker inspect hello-world:latest` ### :material-docker: ps Now it's time to see the `docker ps` command which shows you all containers that are currently running on your machine. ``` docker ps ``` Since no containers are running, you see a blank line. ``` $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES ``` Let's try a more useful variant: `docker ps --all` ``` $ docker ps --all CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES a5eab9243a15 hello-world "/hello" 5 seconds ago Exited (0) 3 seconds ago loving_mcnulty 3bb4e26d2e0c alpine:latest "/bin/sh" 17 seconds ago Exited (0) 16 seconds ago objective_meninsky 192ffdf0cbae opensearchproject/opensearch-dashboards:latest "./opensearch-dashbo…" 3 days ago Exited (0) 3 days ago opensearch-dashboards a10d47d3b6de opensearchproject/opensearch:latest "./opensearch-docker…" 3 days ago Exited (0) 3 days ago opensearch-node1 ``` What you see above is a list of all containers that you have run. Notice that the `STATUS` column shows the current condition of the container: running, or as shown in the example, when the container was exited. ### :material-docker: stop The `stop` command is used for containers that are actively running, either as a foreground process or as a detached background one. You can find a running container using the `docker ps` command. ### :material-docker: rm You can remove individual stopped containers by using the `rm` command. Use the `ps` command to see all your stopped contiainers: ``` @user ➜ /workspaces $ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 03542eaac9dc hello-world "/hello" About a minute ago Exited (0) About a minute ago unruffled_nobel ``` Use the first few unique alphanumerics in the CONTAINER ID to remove the stopped container: ``` @user ➜ /workspaces (mkdocs ✗) $ docker rm 0354 0354 ``` Check to see that the container is gone using `ps -a` a second time (`-a` is shorthand for `--all`; the full command is `docker ps -a` or `docker ps --all`). ### :material-docker: rmi The `rmi` command is similar to `rm` but it will remove the cached images. Used in combination with `docker images` or `docker system df` you can clean up a full cache ``` docker rmi ``` ``` @user ➜ /workspaces/ (mkdocs ✗) $ docker images REPOSITORY TAG IMAGE ID CREATED SIZE opendronemap/webodm_webapp latest e075d13aaf35 21 hours ago 1.62GB redis latest a10f849e1540 5 days ago 117MB opendronemap/nodeodm latest b4c50165f838 6 days ago 1.77GB hello-world latest feb5d9fea6a5 7 months ago 13.3kB opendronemap/webodm_db latest e40c0f274bba 8 months ago 695MB @user ➜ /workspaces (mkdocs ✗) $ docker rmi hello-world Untagged: hello-world:latest Untagged: hello-world@sha256:10d7d58d5ebd2a652f4d93fdd86da8f265f5318c6a73cc5b6a9798ff6d2b2e67 Deleted: sha256:feb5d9fea6a5e9606aa995e879d862b825965ba48de054caab5ef356dc6b3412 Deleted: sha256:e07ee1baac5fae6a26f30cabfe54a36d3402f96afda318fe0a96cec4ca393359 @user ➜ /workspaces (mkdocs ✗) $ ``` ### :material-docker: system The `system` command can be used to view information about containers on your cache, you can view your total disk usage, view events or info. You can also use it to `prune` unused data and image layers. To remove all cached layers, images, and data you can use the `-af` flag for `all` and `force` ``` docker system prune -af ``` ### :material-docker: tag By default an image will recieve the tag `latest` when it is not specified during the `docker build` Image names and tags can be created or changed using the `docker tag` command. ``` docker tag imagename:oldtag imagename:newtag ``` You can also change the registry name used in the tag: ``` docker tag docker.io/username/imagename:oldtag harbor.cyverse.org/project/imagename:newtag ``` The cached image laters will not change their `sha256` and both image tags will still be present after the new tag name is generated. ### :material-docker: push By default `docker push` will upload your local container image to the [Docker Hub](https://hub.docker.com){target=_blank} We will cover `push` in more detail at the end of Day 2, but the essential functionality is the same as pull. Also, make sure that your container has the appropriate [tag](https://unm-carc.github.io/container-camp/docker/intro/#tag) First, make sure to log into the Docker Hub, this will allow you to download private limages, to upload private/public images: ``` docker login ``` Alternately, you can [link GitHub / GitLab accounts](https://hub.docker.com/settings/linked-accounts){target=_blank} to the Docker Hub. To push the image to the Docker Hub: ``` docker push username/imagename:tag ``` or ``` docker push docker.io/username/imagename:tag ``` or, to a private registry, here we push to CyVerse private `harbor.cyverse.org` registry which uses "project" sub folders: ``` docker push harbor.cyverse.org/project/imagename:newtag ``` --- ## Commands & Entrypoints We will cover the differences in `CMD` and `ENTRYPOINT` on Day 2 when we build our own images, but it is important to understand that a container can have a command appended to the `docker run` function. When a image has no commands or entrypoints specified in its Dockerfile, it will default to running a `/bin/sh` syntax. In those cases, you can add a command when the congtainer is run: ``` $ docker run alpine echo "Hello world" ``` the Docker client dutifully ran the ``echo`` command in our ``alpine`` container and then exited. If you've noticed, all of that happened pretty quickly. Imagine booting up a virtual machine, running a command and then killing it. Now you know why they say containers are fast! --- ## Interactive Commands with Containers Lets try another command, this time to access the container as a shell: ``` $ docker run alpine:latest sh ``` Wait, nothing happened, right? Is that a bug? Well, no. The container will exit after running any scripted commands such as `sh`, unless they are run in an "interactive" terminal (TTY) - so for this example to not exit, you need to add the `-i` for interactive and `-t` for TTY. You can run them both in a single flag as ``-it``, which is the more common way of adding the flag: ``` $ docker run -it alpine:latest sh ``` The prompt should change to something more like `/ # `. You are now running a shell inside the container! Try out a few commands like `ls -l`, `uname -a` and others. Exit out of the container by giving the `exit` command. ``` / # exit ``` ??? warning "Making sure you've exited the container" If you type `exit` your **container** will exit and is no longer active. To check that, try the following: ``` $ docker ps --latest CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES de4bbc3eeaec alpine "/bin/sh" 3 minutes ago Exited (0) About a minute ago pensive_leavitt ``` If you want to keep the container active, then you can use keys `ctrl +p` `ctrl +q`. To make sure that it is not exited run the same `docker ps --latest` command again: ``` $ docker ps --latest CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 0db38ea51a48 alpine "sh" 3 minutes ago Up 3 minutes elastic_lewin ``` Now if you want to get back into that container, then you can type `docker attachAdapted from [CyVerse Container Camp](https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/docker/intro.md){target=_blank} (last source update 2023-08-15), CC BY 4.0. Spotted a problem? [Open an issue](https://github.com/UNM-CARC/container-camp/issues){target=_blank}.
---8<--- https://unm-carc.github.io/container-camp/docker/registry/ --- title: "Finding the right container" description: "Container terminology and the public registries (Docker Hub, Quay.io, BioContainers, NVIDIA NGC) to search before building your own image." type: Lesson tags: - Docker - Container Registries generated: by: "claude/opus-5" at: "2026-09-11T00:00:00Z" sources: - id: cyverse-container-camp resource: "https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/docker/registry.md" title: "CyVerse Container Camp: docs/docker/registry.md" author: "team:cyverse" last_modified: "2022-05-06T15:57:18-07:00" --- # Finding the right container There is a high likelihood that the Docker image you need already exists for the application you use in your research. Rather than starting from scratch and creating your own *image* from a `Dockerfile`, you should first go searching for one that already exists. This will save you time writing and compiling your own image. First, you will need to know where to look for existing images. Docker images are hosted across the internet on libraries that are called **Registries**. !!! info "Container Terminology" - **:octicons-container-16: Container**: when an *image* is run it becomes the *container*; A container shares its kernel with other containers using the same image and each runs as an isolated process on the host. - **:material-archive: Registry**: an online library of container images - **:octicons-file-badge-16: Name**: the name of the image - **:material-tag: Tag**: identifies exact version of the image, following a `:` in the name. If no tag name is given, by default Docker will assign the ``latest`` tag name to an image. - **:material-layers: Layer**: an intermediate image, the result of a single set of build commands. An image is built upon layers starting with a base operating system. - **:material-file-edit: Dockerfile**: a text file that contains a list of commands that the Docker daemon calls while creating the layers of an image. - **:material-file: Image**: images are compressed files in a cache on a host and can be built locally or downloaded from a *registry* - **:material-file-code: Base image**: has no parent layers, usually base images are a basic Linux operating system like `alpine`, `debian`, or `ubuntu`. - **:material-file-tree: Child image**: any image built from a base image that has added layers. - **:material-file-check: Official images**: verified (:material-shield-check:) images hosted on a public container registry. Safe to use, built by the professionals who know them best. - **:material-file-star: Publisher image**: certified images that also include support and guarantee compatibility. - **:material-file-question: User image**: images created and shared by users like you. Their contents may be a mystery and therefore are not to be trusted. --- ## Docker Registries Docker uses the concept of **Registries**, online libraries where container **images** are cached for public utilization. ??? question "What *EXACTLY* is a Container Registry?" A Registry is a storage and distribution system for named Docker images Organized by owners into **repositories** with compiled **images** that users can download and run Things you can do with registries: - Search for public images; - Pull official images; - Host and share private images; - Push your images. You must have an account on each registry in order to create repositories and to host your own images. - You can create multiple repositories; - You can create multiple tagged images in a repository; - You can set up your own private registry using a *Docker Trusted Registry*. --- ## Search Image Registries !!! warning Only use images from trusted sources or images for which you can see the `Dockerfile`. Any image from an untrusted source could contain something other than what's indicated. If you can see the Dockerfile you can see exactly what is in the image. Docker looks into the [Docker Hub](https://hub.docker.com/){target=_blank} public registry by default. Examples of public/private registries to consider for your research needs: | Registry Name | Container Types | |---------------|-----------------| | [:material-docker: Docker Hub](https://hub.docker.com/){target=_blank} | Official Images for Docker | | [:material-aws: Amazon Elastic Container Registry](https://aws.amazon.com/ecr/){target=_blank} | run containers on AWS | | [:material-google-cloud: Google Container Registry](https://cloud.google.com/container-registry){target=_blank} | run containers on Google Cloud | | [:material-microsoft-azure: Azure Container Registry](https://docs.microsoft.com/en-us/azure/container-registry/){target=_blank} | run containers on Azure | | [:material-graph: NVIDIA GPU Cloud](https://www.nvidia.com/en-us/gpu-cloud/){target=_blank} | containers for GPU computing | | [:material-github: GitHub Container Registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry){target=_blank} | managed containers on GitHub | | [:material-gitlab: GitLab Container Registry](https://docs.gitlab.com/ee/user/packages/container_registry/){target=_blank} | managed containers on GitLab | | [:material-redhat: RedHat Quay.io](https://cloud.redhat.com/products/quay){target=_blank} | containers managed by RedHat | | [:material-dna: BioContainers Registry](https://biocontainers.pro/){target=_blank} | bioinformatics containers ### :material-docker: DockerHub Docker Hub is a service provided by Docker for finding and sharing container images with your team. Docker Hub is the most well-known and popular image registry for Docker containers. !!! info - **Registry**: a storage and distribution system for named Docker images - **Repository**: collection of "images" with individual "tags". - **Teams & Organizations:** Manages access to private repositories. - **Builds:** Automatically build container images from GitHub or Bitbucket on the Docker Hub. - **Webhooks:** Trigger actions after a successful push to a repository to integrate Docker Hub with other services. ### :material-dna: BioContainers BioContainers is a community-driven project that provides the infrastructure and basic guidelines to create, manage and distribute bioinformatics containers with **special focus in proteomics, genomics, transcriptomics and metabolomics**. BioContainers is based on the popular frameworks of Docker. Although anyone can create a BioContainer, the majority of BioContainers are created by the Bioconda project. Every Bioconda package has a corresponding BioContainer available at Quay.io. ### :material-redhat: RedHat Quay.io Quay is another general image registry. It works the same way as Docker Hub. However, Quay is home to all BioContainers made by the Bioconda project. Now we will find a BioContainer image at Quay, pull that image and run it on cloud virtual machine. ### :material-graph: NVIDIA GPU Cloud NVIDIA is one of the leading makers of graphic processing units (GPU). GPU were established as a means of handling graphics processing operations for video cards, but have been greatly expanded for use in generalized computing applications, Machine Learning, image processing, and matrix-based linear algebras. NVIDIA have created their own set of Docker containers and Registries for running on CPU-GPU enabled systems. - [NVIDIA-Docker](https://github.com/NVIDIA/nvidia-docker){target=_blank} runs atop the NVIDIA graphics drivers on the host system, the NVIDIA drivers are imported to the container at runtime. - [NVIDIA Docker Hub](https://hub.docker.com/u/nvidia){target=_blank} hosts numerous NVIDIA Docker containers, from which you can build your own images. - [NVIDIA GPU Cloud](https://ngc.nvidia.com){target=_blank} hosts numerous containers for HPC and Cloud applications. You must register an account with them (free) to access these. NVIDIA GPU Cloud hosts three [registry spaces](https://docs.nvidia.com/ngc/ngc-user-guide/ngc-spaces.html#ngc-spaces){target=_blank} - `nvcr.io/nvidia` - catalog of fully integrated and optimized deep learning framework containers. - `nvcr.io/nvidia-hpcvis` - catalog of HPC visualization containers (beta). - `nvcr.io/hpc` - popular third-party GPU ready HPC application containers. NVIDIA Docker can be used as a base-image to create containers running graphical applications remotely. High resolution 3D screens are piped to a remote desktop platform.Adapted from [CyVerse Container Camp](https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/docker/registry.md){target=_blank} (last source update 2022-05-06), CC BY 4.0. Spotted a problem? [Open an issue](https://github.com/UNM-CARC/container-camp/issues){target=_blank}.
---8<--- https://unm-carc.github.io/container-camp/docker/build/ --- title: "Building Docker images" description: "Write a Dockerfile instruction by instruction (FROM, RUN, ENV, COPY, CMD, ENTRYPOINT, USER, EXPOSE), including multi-stage builds." type: Lesson tags: - Docker - Dockerfile generated: by: "claude/opus-5" at: "2026-09-11T00:00:00Z" sources: - id: cyverse-container-camp resource: "https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/docker/build.md" title: "CyVerse Container Camp: docs/docker/build.md" author: "team:cyverse" last_modified: "2022-05-12T13:52:06Z" --- # Building Docker images Now that we are relatively comfortable with running Docker, we can look at some advanced Docker topics, such as: - Building our own Docker images from the `Dockerfile` - Modify an existing Dockerfile and create a new image - Push an image to a Registry ## Requirements Clone our example repository with pre-written Dockerfiles From your CodeSpace, we are going to copy a second GitHub repository onto our VM. If you are working locally, make sure that you change directories away from any other Git repository that you may have been working in. ``` $ cd /workspaces $ git clone https://github.com/cyverse-education/intro2docker $ cd intro2docker/ ``` !!! tip "Example Dockerfiles" Copies of the camp's example Dockerfiles are kept in this site's repository under [`examples/`](https://github.com/UNM-CARC/container-camp/tree/main/examples){target=_blank}: a small Python app on [`alpine`](https://github.com/UNM-CARC/container-camp/tree/main/examples/alpine){target=_blank}, the `fortune | cowsay | lolcat` image built in this lesson ([`cowsay`](https://github.com/UNM-CARC/container-camp/tree/main/examples/cowsay){target=_blank}), an iRODS client image on [`ubuntu`](https://github.com/UNM-CARC/container-camp/tree/main/examples/ubuntu){target=_blank}, and a Jupyter image ([`jupyter/minimal-notebook`](https://github.com/UNM-CARC/container-camp/tree/main/examples/jupyter/minimal-notebook){target=_blank}). --- ## Writing a Dockerfile !!! note "Important" `Dockerfile` must be capitalized. It does not have a file extension. Create a file called `Dockerfile`, and add content to it as described below, e.g. ``` $ touch Dockerfile ``` !!! note "Formatting in the `Dockerfile`" We use a code line escape character `\` to allow single line scripts to be written on multiple lines in the Dockerfile. We also use the double characters `&&` which essentially mean “if true, then do this” while executing the code. The `&&` can come at the beginning of a line or the end when used with `\`. The `Dockerfile` contains **Instructions**: a series of commands that Docker executes during the creation and execution of a container. ---- ### ARG The only command that can come before a `FROM` statement is `ARG` `ARG` can be used to set arguments for later in the build, e.g., ``` ARG VERSION=latest FROM ubuntu:$VERSION ``` ### FROM A valid `Dockerfile` must start with a `FROM` statement which initializes a new build stage and sets the **base image** for subsequent layers. We’ll start by specifying our base image, using the FROM statement ``` FROM ubuntu:latest ``` If you are building on an `arm64` or Windows system you can also give the optional `--platform` flag, e.g., ``` FROM --platform=linux/amd64 ubuntu:latest ``` ??? question "When to use a multi-stage build pattern?" Docker has the ability to build container images from one image, and run that "builder" image from a second "base" image, in what is called a "builder pattern". Build patterns are useful if you're compiling code from (proprietary) source code and only want to feature the binary code as an executed function in the container at run time. Build patterns can greatly reduce the size of your container. You can use multiple `FROM` commands as build stages. The `AS` statement follows the `image:tag` as a psuedo argument. ``` # build stage FROM golang:latest AS build-env WORKDIR /go/src/app ADD . /go/src/app RUN go mod init RUN cd /go/src/app && go build -o hello # final stage FROM alpine:latest WORKDIR /app COPY --from=build-env /go/src/app /app/ ENTRYPOINT ./hello ``` A runnable copy of this two-stage build, with a single-stage version for comparison, is in [`examples/builder`](https://github.com/UNM-CARC/container-camp/tree/main/examples/builder){target=_blank}; build both and compare their sizes with `docker images`. ### LABEL You can create labels which are then tagged as JSON metadata to the image ``` LABEL author="your-name" LABEL email="your@email-address" LABEL version="v1.0" LABEL description="This is your first Dockerfile" LABEL date_created="2022-05-13" ``` You can also add labels to a container when it is run: ``` $ docker run --label description="this label came later" ubuntu:latest $ docker ps -a $ docker inspect ### ``` ### RUN Different than the `docker run` command is the `RUN` build function. `RUN` is used to create new layers atop the "base image" Here, we are going to install some games and programs into our base image: ``` RUN apt-get update && apt-get install -y fortune cowsay lolcat ``` Here we've installed `fortune` `cowsay` and `lolcat` as new programs into our base image. !!! warning "Best practices for building new layers" Ever time you use the `RUN` command it is a good idea to use the `apt-get update` or `apt update` command to make sure your layer is up-to-date. This can become a problem though if you have a very large container with a large number of `RUN` layers. ### ENV In our new container, we need to change and update some of the environment flags. We can do this using the `ENV` command ``` ENV PATH=/usr/games:${PATH} ENV LC_ALL=C ``` Here we are adding the `/usr/games` directory to the `PATH` so that when we run the new container it will find our newly installed game commands We are also updating the "[locales](https://www.tecmint.com/set-system-locales-in-linux/){target=_blank}" to set the language of the container. ### COPY The `COPY` command will copy files from the directory where `Dockerfile` is kept into the new image. You must specify where to copy the files or directories ``` COPY . /app ``` ??? question "When to use `COPY` vs `ADD`" `COPY` is more basic and is good for files `ADD` has some extra features like `.tar` extraction and URL support ### CMD The `CMD` command is used to run software in your image. In general use the ["command"] syntax: ``` CMD ["executable", "parameter1", "parameter2"] ``` ### ENTRYPOINT ENTRYPOINT works similarly to `CMD` but is designed to allow you to run your container as an executable. ``` ENTRYPOINT fortune | cowsay | lolcat ``` The default `ENTRYPOINT` of most images is `/bin/sh -c` which executes a `shell` command. `ENTRYPOINT` supports both the `ENTRYPOINT ["command"]` syntax and the `ENTRYPOINT command` syntax ??? question "What is the difference in the `ENTRYPOINT` and `CMD`" The CMD instruction is used to define what is execute when the container is run. The ENTRYPOINT instruction cannot be overridden, instead it is appended to when a new command is given to the `docker run container:tag new-cmd` statement the executable is defined with ENTRYPOINT, while CMD specifies the default parameter ### USER Most containers are run as `root` meaning that they have super-user privileges within themselves Typically, a new user is necessary in a container that is used interactively or may be run on a remote system. During the build of the container, you can create a new user with the `adduser` command and set up a `/home/` directory for them. This new user would have something like 1000:1000 `uid:gid` permissions without `sudo` privileges. As a last step, the container is run as the new `USER`, e.g., ``` ARG VERSION=18.04 FROM ubuntu:$VERSION RUN useradd ubuntu && \ chown -R ubuntu:ubuntu /home/ubuntu USER ubuntu ``` ### EXPOSE You can open [ports](https://unm-carc.github.io/container-camp/docker/intro/#understanding-ports) using the `EXPOSE` command. ``` EXPOSE 8888 ``` The above command will expose port 8888. The [`examples/jupyter/minimal-notebook`](https://github.com/UNM-CARC/container-camp/tree/main/examples/jupyter/minimal-notebook){target=_blank} Dockerfile uses `EXPOSE 8888` for its Jupyter server. !!! note Running multiple containers using the same port is not trivial and would require the usage of a web server such as [NGINX](https://www.nginx.com/){target=_blank}. However, you can have multiple containers interact with each other using [Docker Compose](https://unm-carc.github.io/container-camp/docker/compose/). --- ## Summary of Instructions | Instruction Command | Description | | --- | --- | | `ARG` | Sets environmental variables during image building | | `FROM` | Instructs to use a specific Docker image | | `LABEL` | Adds metadata to the image | | `RUN` | Executes a specific command | | `ENV` | Sets environmental variables | | `COPY` | Copies a file from a specified location to the image | | `CMD` | Sets a command to be executed when running a container | | `ENTRYPOINT` | Configures and run a container as an executable | | `USER` | Used to set User specific information | | `EXPOSE` | exposes a specific port |Adapted from [CyVerse Container Camp](https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/docker/build.md){target=_blank} (last source update 2022-05-12), CC BY 4.0. Spotted a problem? [Open an issue](https://github.com/UNM-CARC/container-camp/issues){target=_blank}.
---8<--- https://unm-carc.github.io/container-camp/docker/advanced/ --- title: "Advanced Docker: volumes and bind mounts" description: "Create, inspect, and populate Docker volumes, and use read-write and read-only bind mounts to share host data with containers." type: Lesson tags: - Docker - Volumes - Data generated: by: "claude/opus-5" at: "2026-09-11T00:00:00Z" sources: - id: cyverse-container-camp resource: "https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/docker/advanced.md" title: "CyVerse Container Camp: docs/docker/advanced.md" author: "team:cyverse" last_modified: "2022-04-25T15:46:54-07:00" --- # Advanced Docker: volumes and bind mounts ## Volumes When you run a container, you can bring a directory from the host system into the container, and give it a new name and location using the `-v` or `--volume` flag. ``` bash $ mkdir -p ~/local-data-folder $ echo "some data" >> ~/local-data-folder/data.txt $ docker run -v ${HOME}/local-data-folder:/data $YOUR_DOCKERHUB_USERNAME/mynotebook:latest cat /data/data.txt ``` In the example above, you can mount a folder from your localhost, in your home user directory into the container as a new directory named `/data`. ### Create and manage volumes Unlike a bind mount, you can create and manage volumes outside the scope of any container. A given volume can be mounted into multiple containers simultaneously. When no running container is using a volume, the volume is still available to Docker and is not removed automatically. You can remove unused volumes using `docker volume prune` command. When you create a Docker volume, it is stored within a directory on the Docker Linux host (`/var/lib/docker/`). !!! note File location on Mac OS X is a bit different:Adapted from [CyVerse Container Camp](https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/docker/advanced.md){target=_blank} (last source update 2022-04-25), CC BY 4.0. Spotted a problem? [Open an issue](https://github.com/UNM-CARC/container-camp/issues){target=_blank}.
---8<--- https://unm-carc.github.io/container-camp/docker/actions/ --- title: "GitHub Actions and Docker" description: "Build and push Docker images to Docker Hub automatically with a GitHub Actions workflow and repository secrets." type: Lesson tags: - Docker - GitHub Actions - CI/CD generated: by: "claude/opus-5" at: "2026-09-11T00:00:00Z" sources: - id: cyverse-container-camp resource: "https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/docker/actions.md" title: "CyVerse Container Camp: docs/docker/actions.md" author: "team:cyverse" last_modified: "2022-05-16T17:03:29-07:00" --- # GitHub Actions and Docker [GitHub Actions](https://github.com/features/actions?gclid=EAIaIQobChMI6JzCw9bf9wIVhD-tBh11sQ42EAAYASABEgKxrvD_BwE){target=_blank} is a feature that allows automation and execution of workflows invoved in the development of your software and code. Read more on GitHub Actions at the [offical GitHub Docs page](https://docs.github.com/en/actions/learn-github-actions/understanding-github-actions){target=_blank}. Actions can also be used to create and manage your Docker Images. Here you will learn how to can create Actions which build and then push images from GitHub to a Docker Registry of your choice. --- ### Prerequisites - A [GitHub](https://github.com/){target=_blank} account - A [Docker](https://hub.docker.com/){target=_blank} account ## Setting up a Git Repository with Actions for Docker ??? info "CI/CD Terminology" * **:material-infinity: continuous integration** builds, tests, and automatically accepts changes to code in a repository * **:material-infinity: continuous delivery** delivers code changes to production-ready environments * **:material-infinity: continuous deployment** does just that, it deploys changes to the code * **:material-pipe: CI pipeline** compiles code, tests it, and makes sure all of your changes work. It should run whenever there is a change (push) to the repository * **:material-pipe: CD pipeline** goes one step further and deploys new code into production. We are focusing on GitHub, but there are other platforms which you can explore for building and pushing containers to registries These include [GitLab Runners](https://docs.gitlab.com/runner/){target=_blank} Other types of Continuous Integration are used on code repositories to ensure that code stays functional. ### Create a GitHub Repository Navigate to your GitHub Account and select **New**;  ??? tip ":material-github: Creating README, LICENSE, & .gitignore" When you create your new git repository you are asked to create a `README`, a `LICENCE`, and a `.gitignore` file. Go ahead and create all three of these, as they are useful and fundamental to making your repository reproducible. README -- we want to use the README to help future-us when we revisit this repository. Make sure to include detailed instructions LICENSE -- pick a license which is useful for your specific software use case. `.gitignore` -- use a file which will keep docker project files isolated ??? info ":material-docker: `.gitignore` example" ``` # Docker project generated files to ignore # if you want to ignore files created by your editor/tools, # please consider a global .gitignore https://help.github.com/articles/ignoring-files .vagrant* bin docker/docker .*.swp a.out *.orig build_src .flymake* .idea .DS_Store docs/_build docs/_static docs/_templates .gopath/ .dotcloud *.test bundles/ .hg/ .git/ vendor/pkg/ pyenv Vagrantfile ``` In the repository, create two nested folders required for a Docker Actions project: - A `.github/workflows` folder: containing required necessary `yml` files that build the containers through GitHub; In the workflows folder, we're going to add a `.yml` In the main repository, along with the `README` and `LICENCE` file, create another folder called `/docker` In the `/docker` folder we're going to put the `Dockerfile` file necessary to build the image. ### Link your GitHub and Docker accounts Ensure you can access [Docker Hub](https://hub.docker.com/){target=_blank} from any workflows you create: 1. Add your Docker ID as a secret to GitHub. Navigate to your GitHub repository and click **Settings > Secrets and variables > Actions > New repository secret**.  2\. Create a new secret with the name `DOCKER_HUB_USERNAME` and your Docker ID as value. 3\. On DockerHub, create a new Personal Access Token (PAT). To create a new token, go to [Docker Hub Settings](https://hub.docker.com/settings/security){target=_blank} and then click **New Access Token**. Name it, and copy the newly generated PAT. ??? tip Name your Docker Hub Access token the same name as your GitHub repository, it will help with keeping track which GitHub repository is related to which Docker image.  4. On GitHub, return to your repository secrets, and add the PAT as a secret with the name `DOCKER_HUB_ACCESS_TOKEN`.  --- ## Setting up a GitHub Action Workflow Now that you have connected your GitHub repository with your Docker account, you are ready to add the necessary files to your repo. !!! note In this example, we will use the existing Docker Image Alpine. 1\. In your GitHub repository, create a file and name in `Dockerfile`; In the first line of your `Dockerfile` paste: ``` FROM alpine:3.14 ``` 2\. Click the `Actions` tab and in the search bar, search for `docker`. Select the `docker image workflow` (as shown in the image below) !!! note This will create the `.github/workflows` repository and necessary `yml` file required for the GitHub actions.  3\. You will be prompted to the `docker-image.yml` file; paste the following code, and `commit` your changes. ``` name: Docker Image Small Alpine on: push: branches: [ main ] pull_request: branches: [ main ] jobs: build: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Login to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} - name: Build and push uses: docker/build-push-action@v6 with: context: . file: ./Dockerfile push: true tags: ${{ secrets.DOCKER_HUB_USERNAME }}/simplewhale ``` 4\. Upon committing and pushing your changes, you can check your Workflows under the Actions tab on GitHub. !!! note Github will show you when a workflow is building: - An orange dot next to your commit count means that the workflow is running; - A crossed red circle means that your workflow has failed; - A green check means your workflow ran successfully.  5\. Navigate to your Docker Hub to see your GitHub Actions generated Docker image.   --- ## Tagging a Pushing an Image When updating your own Docker Images, you may want to tag the images with a version number. In this example, we update our Alpine Docker Image to a newer version and adding a package. Navigate to your `Dockerfile` and modify it by adding the following lines: ``` FROM alpine:3.15 LABEL author="Adapted from [CyVerse Container Camp](https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/docker/actions.md){target=_blank} (last source update 2022-05-16), CC BY 4.0. Spotted a problem? [Open an issue](https://github.com/UNM-CARC/container-camp/issues){target=_blank}.
---8<--- https://unm-carc.github.io/container-camp/docker/compose/ --- title: "Multi-container applications with Docker Compose" description: "Define and run Jupyter and RStudio together from one Compose file, manage the stack with docker compose, and deploy WebODM." type: Lesson tags: - Docker - Docker Compose generated: by: "claude/opus-5" at: "2026-09-11T00:00:00Z" sources: - id: cyverse-container-camp resource: "https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/docker/compose.md" title: "CyVerse Container Camp: docs/docker/compose.md" author: "team:cyverse" last_modified: "2022-05-12T19:53:24-07:00" --- # Multi-container applications with Docker Compose [:material-docker: Docker Compose](https://docs.docker.com/compose){target=_blank} is an extension of Docker which allows you to run multiple containers synchronously and in communication with one another. Compose allows you to define and run a multi-container service using a `Dockerfile` and a `docker-compose.yml`. !!! note "Installing Compose" Compose v2 runs as a Docker CLI plugin: you type `docker compose` (with a space). It is included with Docker Desktop on macOS and Windows. On Linux, install the `docker-compose-plugin` package from Docker's package repository, for example on Ubuntu: ``` bash sudo apt-get install docker-compose-plugin docker compose version ``` The older standalone `docker-compose` (v1) command is no longer maintained, so the commands on this page use `docker compose`. Main advantages of Docker compose include: - Your applications can be defined in a YAML file where all the same options required in `docker run` are now defined (reproducibility). - It allows you to manage your application(s) as a single entity rather than dealing with starting individual containers (simplicity). !!! note For the sake of this example, either create your own `Dockerfile` or use the same Jupyter SciPy Notebook as in the [Advanced Section](https://unm-carc.github.io/container-camp/docker/advanced/) --- ## Creating a `docker-compose.yml` Let's now create a Docker Compose `.yml` that calls Jupyter Notebook and RStudio 1\. Create a folder `shared_data` in your current directory: ``` mkdir shared_data ``` 2\. Create an empty `docker-compose.yml` file (e.g., touch docker-compose.yml) and paste the following lines. Compose v2 ignores the top-level `version:` key found in older examples, so it is omitted here. ``` # All available services services: # Computation jupyter: container_name: "jupyter_notebook" image: "jupyter/minimal-notebook" restart: "always" environment: - JUPYTER_TOKEN=mytoken user: root volumes: - ./data:/home/jovyan/work/ ports: - 8888:8888 rstudio: container_name: "rstudio" image: "rocker/rstudio" restart: "always" environment: - DISABLE_AUTH=true volumes: - ./data:/home/rstudio ports: - 8787:8787 ``` 3\. Run both Jupyter Lab and RStudio using `docker compose up` instead of `docker run`. !!! note Handling containers with Docker Compose is fairly simple ``` bash docker compose up ``` attaches the volumes, opens ports, and starts the container ``` bash docker compose down ``` destroys the container A brief explanation of `docker-compose.yml` is as below: - The web service builds from the Dockerfile in the current directory. - Forwards the container's exposed port to port 8888 on the host. - Mounts the project directory on the host to `/work` or `/rstudio` inside the container (allowing you to modify code without having to rebuild the image). - `restart: always` means that it will restart whenever it fails. --- ## Running, shutting down, and restarting with `docker compose` Run the containers with ``` $ docker compose up -d ``` To stop a running `docker compose` session, either press `CTRL + C` or use the command: ``` docker compose down ``` The above command removes containers, networks, volumes and images created by `docker compose up`. To restart a container, use the command ``` docker compose restart ``` `restart` will restart the Compose services *without* taking into account changes one may have made to the `yml` or environment. --- ## Example using Docker Compose: WebODM !!! warning For the purpose of these following examples it is not suggested to use GitHub Codespaces. [:material-quadcopter: Web Open Drone Map (WebODM)](https://github.com/OpenDroneMap/WebODM/#run-it-on-the-cloud-google-compute-amazon-aws){target=_blank} OpenDroneMap is an open source photogrammetry toolkit to process aerial imagery into maps and 3D models running on command line. WebODM (Web OpenDroneMap) is an extension of ODM running on multiple Docker Containers provinding a user friendly web interface for easy visualization. To use WebODM: !!! note "Prerequisites" WebODM requires `docker` and Docker Compose to function. Additionally, if you are on Windows, users will be required to have the [Docker Windows Application](https://docs.docker.com/desktop/windows/install/){target=_blank} installed as well as having the [WSL2](https://docs.microsoft.com/en-us/windows/wsl/install){target=_blank} (Windows Subsystem for Linux) operational. 1. Ensure your machine is up to date: `sudo apt-get update` 2. Clone the WebODM repository: `git clone https://github.com/OpenDroneMap/WebODM --config core.autocrlf=input --depth 1` 3. Move into the WebODM folder: `cd WebODM` 4. Run WebODM: `sudo ./webodm.sh start` 5. The necessary docker images will be downloaded (~2 minutes) and WebODM will be accessible through http://localhost:8000/ !!! note You will be asked to create an account as a formality. Add any *username* and a *password* and select **Create Account**. 6\. Download example data: `git clone https://github.com/OpenDroneMap/odm_data_aukerman.git`. This git repository contains 77 HD images usable for WebODM. For other examples refer to [ODMData](https://www.opendronemap.org/odm/datasets/){target=_blank}. 7\. In the WebODM portal, click on **Select Images and GCP**, navigate to `odm_data_aukerman/images` and select between 20-50 images (16 is the absolute minimum, whilst 32 is the suggested minimum).  8\. WebODM will process the uploaded images (~5-10 minutes); upon completion, click **View Map**.  9\. A map will open; you can click on **3D** (bottom right) to see the 3D rendered model generated. Adapted from [CyVerse Container Camp](https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/docker/compose.md){target=_blank} (last source update 2022-05-12), CC BY 4.0. Spotted a problem? [Open an issue](https://github.com/UNM-CARC/container-camp/issues){target=_blank}.
---8<--- https://unm-carc.github.io/container-camp/singularity/intro/ --- title: "Introduction to Apptainer/Singularity" description: "Install Apptainer or SingularityCE and use the CLI to pull, shell, exec, run, and inspect container images, including images from Docker Hub." type: Lesson tags: - Apptainer - Singularity - Containers - HPC generated: by: "claude/opus-5" at: "2026-09-11T00:00:00Z" sources: - id: cyverse-container-camp resource: "https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/singularity/intro.md" title: "CyVerse Container Camp: docs/singularity/intro.md" author: "team:cyverse" last_modified: "2023-03-07T20:36:53-07:00" --- # Introduction to Apptainer/Singularity In this section we work with [Apptainer](https://apptainer.org/){target=_blank} and [Singularity Community Edition (SingularityCE)](https://sylabs.io/guides/3.9/user-guide/index.html){target=_blank}. Apptainer is the Linux Foundation continuation of the original Singularity project; it still installs `singularity` as an alias of the `apptainer` command, so the `singularity ...` commands in these lessons work with either runtime. SingularityCE is the fork maintained by Sylabs. ??? question "Wait, what is "Apptainer", and what is the difference between SingularityCE and Apptainer?" The Singularity project was split into multiple projects managed by different organizations since it was created in 2017. In a nutshell: - Greg Kurtzer founded the Singularity project while at the Lawrence Berkeley National Laboratory - Kurtzer created [Sylabs](https://sylabs.io/){target=_blank}, a private company, around Singularity - Kurtzer left Sylabs to focus on [CIQ](https://ciq.co/){target=_blank}, another private company, and moved Singularity to [HPCng](https://hpcng.org/){target=_blank} (a Community Org) - [Sylabs forked Singularity](https://sylabs.io/singularityce-community-update/){target=_blank} for control and professionially licensed support creating Singularity Community Edition" - HPCng gave the official project to Linux Foundation and renamed it "[Apptainer](https://apptainer.org/){target=_blank}" - Apptainer is being [marketed by CIQ](https://ciq.co/apptainer/){target=_blank} At the present time, Apptainer and SingularityCE have highly similar syntax and will run Singularity `.sif` images interoperably. Apptainer keeps `singularity` as a command alias, so existing scripts keep working. ??? info "Docker vs SingularityCE & Apptainer" **:material-open-source-initiative: Apptainer and SingularityCE are 100% compatible with Docker but they do have some distinct differences** **:material-docker: Docker** :octicons-container-24: Docker containers run as `root` - This privilege is almost never supported by administrators of High Performance Computing (HPC) centers. Meaning Docker is not, and will likely never be, installed natively on your HPC cluster. :octicons-container-24: uses compressed layers to create one image **:material-open-source-initiative: SingularityCE & Apptainer**: :octicons-container-24: Same user and group identity inside as outside the container :octicons-container-24: User only has `root` privileges if elevated with `sudo` when the container is run :octicons-container-24: Can run and modify any existing Docker image - These key differences allow Singularity to be installed on most HPC centers. Because you can run virtually all Docker containers in Singularity, you can effectively run Docker on an HPC. ## Installing Apptainer or SingularityCE Sylabs Singularity Community Edition (CE) homepage: [https://www.sylabs.io/docs/](https://www.sylabs.io/docs/){target=_blank} Apptainer Linux Foundation homepage: [https://apptainer.org/](https://apptainer.org/){target=_blank} ### Conda [SingularityCE](https://anaconda.org/conda-forge/singularityce){target=_blank} or [Apptainer](https://anaconda.org/conda-forge/apptainer){target=_blank} can both be installed from Conda: ``` conda install -c conda-forge singularityce ``` ``` conda install -c conda-forge apptainer ``` ### Install Locally To Install Singularity follow the instructions for your specific OS: [:simple-github: SingularityCE](https://github.com/sylabs/singularity/blob/main/INSTALL.md){target=_blank} [:material-book-open-variant: Apptainer](https://apptainer.org/docs/admin/main/installation.html){target=_blank} ### Module loading on HPC If you are interested in working with SingularityCE on HPC, you may need to contact your systems administrator and request they install [SingularityCE](https://sylabs.io/guides/3.9/user-guide/quick_start.html#installation-request){target=_blank}. Because SingularityCE ideally needs setuid, your admins may have some qualms about giving SingularityCE this privilege. If that is the case, you might consider forwarding [this letter](https://sylabs.io/guides/3.9/user-guide/quick_start.html#Singularity-on-a-shared-resource){target=_blank} to your admins. Most HPC systems are running Environment Modules with the simple command `module`. You can check to see what is available: ``` $ module avail singularity ``` If Singularity is listed as being installed, load a specific version, e.g.: ``` $ module load singularity/3/3.9 ``` On some systems the module is named `apptainer` instead (`module avail apptainer`). !!! tip "UNM CARC users" The CARC documentation has a guide to [running Singularity/Apptainer containers on CARC clusters](https://carc.unm.edu/docs/software/singularity/){target=_blank}. Run containers inside Slurm jobs rather than on the login nodes; see the [CARC introduction to Slurm](https://carc.unm.edu/docs/running-jobs/slurm-intro/){target=_blank}. ### Install in CodeSpaces Let's use Conda (or optionally, Mamba) ``` conda install -c conda-forge singularityce mamba install -c conda-forge singularityce ``` ## Apptainer and Singularity CLI Singularity’s [command line interface](https://sylabs.io/guides/3.9/user-guide/cli.html){target=_blank} allows you to build and interact with containers transparently. You can run programs inside a container as if they were running on your host system. You can easily redirect IO, use pipes, pass arguments, and access files, sockets, and ports on the host system from within a container. ### :octicons-container-24: help The `help` command gives an overview of Singularity options and subcommands as follows: ``` $ singularity help pull Pull an image from a URI Usage: singularity pull [pull options...] [output file]Adapted from [CyVerse Container Camp](https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/singularity/intro.md){target=_blank} (last source update 2023-03-07), CC BY 4.0. Spotted a problem? [Open an issue](https://github.com/UNM-CARC/container-camp/issues){target=_blank}.
---8<--- https://unm-carc.github.io/container-camp/singularity/advanced/ --- title: "Building Apptainer/Singularity images" description: "Build SIF images from Docker images, sandboxes, and definition files, with the %post, %environment, %runscript, and %labels sections explained." type: Lesson tags: - Apptainer - Singularity - Definition Files generated: by: "claude/opus-5" at: "2026-09-11T00:00:00Z" sources: - id: cyverse-container-camp resource: "https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/singularity/advanced.md" title: "CyVerse Container Camp: docs/singularity/advanced.md" author: "team:cyverse" last_modified: "2022-05-16T18:42:04-07:00" --- # Building Apptainer/Singularity images ## Apptainer and Singularity CLI continued ### Build Singularity `.sif` images Similar to Docker which uses a `Dockerfile` to build its images, Singularity uses a file called `Singularity`. Apptainer and SingularityCE call this file a *definition file*; it is often named `Singularity` or given a `.def` extension. **Important** * Singularity images use the `.sif` extension and appear as a single compressed file, versus Docker which uses many cached file layers to create a single image. * `.sif` files also use layers, but these are not apparent. * `.sif` images are cached in the folder where you build them, or designate them. * When building from `docker://` the Docker image layers are downloaded and cached by Singularity in a `/.Singularity` folder on your build environment. ### :octicons-container-24: build ### Create a `.sif` image using a Docker image as the template As we've learned from the HPC and HTC groups, building Singularity images is not necessarily the most accessible and reproducible method for managing containers. Most groups suggest that you build your containers with Docker, and host them on a Docker Registry first. The market dominance of Docker and its wide acceptance as a container format, has led us to use Singularity with Docker in most cases. We've already covered how you can pull an existing image from Docker Hub, but we can also build a Singularity image from the Docker Hub using the build command: ``` $ sudo singularity build --sandbox ubuntu-latest/ docker://ubuntu ``` Test the new `.sif` image: ``` $ singularity shell --writable ubuntu-latest/ Singularity ubuntu-latest.sif:~> apt-get update ``` Does it work? ``` $ sudo singularity shell ubuntu-latest.sif Singularity: Invoking an interactive shell within container... Singularity ubuntu-latest.sif:~> apt-get update ``` When I try to install software to the image without `sudo` it is denied, because `root` is the owner inside the container. When I use `sudo` I can install software into the container. The software remains in the sandbox container after closing the container and restart. In order to make these changes permanant, I need to rebuild the sandbox as a `.sif` image ``` $ sudo singularity build ubuntu-latest.sif ubuntu-latest/ ``` ## Creating Singularity `.sif` from scratch The contents of the `Singularity` file differ from `Dockerfile` - `%help` - create text for a help menu associated with your container - `%setup` - executed on the host system outside of the container, after the base OS has been installed. - `%files` - copy files from your host system into the container - `%labels` - store metadata in the container - `%environment` - loads environment variables at the time the container is run (not built) - `%post` - set environment variables during the build - `%runscript` - executes a script when the container runs - `%test` - runs a test on the build of the container | Dockerfile | FROM | ARG | COPY | RUN | ENV | LABEL | CMD | |--------|------|-----|------|-----|-----|-------|-----| | Singularity | `Bootstrap:` | `%post` | `%files` | `%post` | `%environment` | `%label` | `%runscript` | ### Writing the `Singularity` file [SyLabs User-Guide](https://sylabs.io/guides/3.9/user-guide/){target=_blank} A `Singularity` file can be hosted on GitHub next to the code it packages. (Singularity Hub, which once built these files automatically, has been retired; build images yourself or in a CI service such as [GitHub Actions](https://unm-carc.github.io/container-camp/docker/actions/).) When you are building locally, you can name the `Singularity` file whatever you wish, but a better practice is to put it in a specified directory and name it `Singularity`. Building your own containers requires that you have sudo privileges - therefore you'll need to develop these on your local machine or on a VM that you can gain root access on. #### :material-layers-edit: **Header** The top of the file, selects the base OS for the container, just like `FROM` in Docker. `Bootstrap:` references another registry (e.g. `docker` for DockerHub, `debootstrap`, or `library` for Sylabs [Container Library](https://cloud.sylabs.io/library){target=_blank}). `From:` selects the tag name. Using `debootstrap` with a build that uses a mirror: ``` BootStrap: debootstrap OSVersion: jammy MirrorURL: http://us.archive.ubuntu.com/ubuntu/ ``` Using CentOS-like container: ``` Bootstrap: yum OSVersion: 7 MirrorURL: http://mirror.centos.org/centos-7/7/os/x86_64/ Include:yum ``` Note: to use yum to build a container you should be operating on a RHEL system, or an Ubuntu system with yum installed. Using a localimage to build: ``` Bootstrap: localimage From: /path/to/container/file/or/directory ``` The container registries which Singularity uses are listed in the [introduction](https://unm-carc.github.io/container-camp/singularity/intro/#downloading-pre-built-images). - The Singularity file uses sections to specify the dependencies, environmental settings, and runscripts when it builds. #### :material-layers-edit: **help** `%help` section can be as verbose as you want ``` Bootstrap: docker From: ubuntu %help This is the container help section. ``` #### :material-layers-edit: **setup** `%setup` commands are executed on the localhost system outside of the container - these files could include necessary build dependencies. We can copy files to the `$SINGULARITY_ROOTFS` file system can be done during setup #### :material-layers-edit: **files** `%files` include any files that you want to copy from your localhost into the container. #### :material-layers-edit: **post** `%post` includes all of the environment variables and dependencies that you want to see installed into the container at build time. ``` %post apt-get -y update apt-get -y install fortune cowsay lolcat ``` #### :material-layers-edit: **environment** `%environment` includes the environment variables which we want to be run when we start the container ``` %environment export LC_ALL=C export PATH=/usr/games:$PATH ``` #### :material-layers-edit: **runscript** `%runscript` does what it says, it executes a set of commands when the container is run. ``` %runscript fortune | cowsay | lolcat ``` #### :material-layers-edit: **labels** `%labels` are used similar to Dockerfile which allow you to add metadata to the image file in key-value pairs ``` %labels Author Your Name Email email@address.org Version v2022 CustomLabel statement here ``` Labels can be read using the `inspect` command #### Example File Example `Singularity` file bootstrapping an Ubuntu (22.04) image. ``` Bootstrap: docker From: ubuntu:22.04 %post apt-get -y update apt-get -y install fortune cowsay lolcat %environment export LC_ALL=C export PATH=/usr/games:$PATH %runscript fortune | cowsay | lolcat %labels Maintainer Your Name Version v2022 ``` Build the container: ``` singularity build cowsay.sif Singularity ``` Run the container: ``` singularity run cowsay.sif ``` ## Sandbox Sandboxing is another approach to building up a container image.Adapted from [CyVerse Container Camp](https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/singularity/advanced.md){target=_blank} (last source update 2022-05-16), CC BY 4.0. Spotted a problem? [Open an issue](https://github.com/UNM-CARC/container-camp/issues){target=_blank}.
---8<--- https://unm-carc.github.io/container-camp/singularity/hpc/ --- title: "Apptainer/Singularity on HPC" description: "Run containers through a Slurm batch scheduler, match MPI versions between host and container, and use GPUs with the --nv flag." type: Lesson tags: - Apptainer - HPC - Slurm - MPI - GPU generated: by: "claude/opus-5" at: "2026-09-11T00:00:00Z" sources: - id: cyverse-container-camp resource: "https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/singularity/hpc.md" title: "CyVerse Container Camp: docs/singularity/hpc.md" author: "team:cyverse" last_modified: "2022-04-12T17:50:10-07:00" --- # Apptainer/Singularity on HPC High Performance Computing resources fill an important role in research computing and can support container execution through runtimes such as Apptainer/Singularity, among other options. Conducting analyses on HPC clusters happens through different patterns of interaction than running analyses on a cloud VM. When you login, you are on a node that is shared with lots of people, typically called the "login node". Trying to run jobs on the login node is not "high performance" at all (and will likely get you an admonishing email from the system administrator). Login nodes are intended to be used for moving files, editing files, and launching jobs. Importantly, most jobs run on an HPC cluster are neither **interactive**, nor **realtime**. When you submit a job to the scheduler, you must tell it what resources you need (e.g. how many nodes, how much RAM, what type of nodes, and for how long) in addition to what you want to run. Then the scheduler finally has resources matching your requirements, it runs the job for you. If your request is very large, or very long, you may never make it out of the queue. For example, on a VM if you run the command: ``` singularity exec docker://python:latest /usr/local/bin/python ``` The container will immediately start. On an HPC system, your job submission script would look something like: ``` #!/bin/bash # #SBATCH -J myjob # Job name #SBATCH -o output.%j # Name of stdout output file (%j expands to jobId) #SBATCH -p development # Queue name #SBATCH -N 1 # Total number of nodes requested (68 cores/node) #SBATCH -n 17 # Total number of mpi tasks requested #SBATCH -t 02:00:00 # Run time (hh:mm:ss) - 4 hours module load singularity/3/3.1 singularity exec docker://python:latest /usr/local/bin/python ``` This example is for the Slurm scheduler. Each of the \#SBATCH lines looks like a comment to the bash kernel, but the scheduler reads all those lines to know what resources to reserve for you. It is usually possible to get an interactive session as well, by using an interactive flag, -i. !!! tip "UNM CARC users" On CARC clusters, run container workloads through Slurm as shown above. The [CARC Singularity guide](https://carc.unm.edu/docs/software/singularity/){target=_blank} covers the container runtime on CARC systems, and the [CARC introduction to Slurm](https://carc.unm.edu/docs/running-jobs/slurm-intro/){target=_blank} explains CARC partitions and resource requests. !!! warning Every HPC cluster is a little different, but they almost universally have a "User's Guide" that serves both as a quick reference for helpful commands and contains guidelines for how to be a "good citizen" while using the system. For TACC's Stampede2 system, see the [user guide](https://portal.tacc.utexas.edu/user-guides/stampede2){target=_blank}. For The University of Arizona, see the [user guide](https://docs.hpc.arizona.edu/){target=_blank}. ## How do HPC systems fit into the development workflow? A few things to consider when using HPC systems: 1. Using `sudo` is not allowed on HPC systems, and building a Singularity container from scratch requires sudo. That means you have to build your containers on a different development system. You can pull a docker image on HPC systems 2. If you need to edit text files, command line text editors don't support using a mouse, so working efficiently has a learning curve. There are text editors that support editing files over SSH. This lets you use a local text editor and just save the changes to the HPC system. These constraints make HPC systems perfectly suitable for execution environments, but currently a limiting choice for a development environment. We usually recommend your local laptop or a VM as a development environment where you can iterate on your code rapidly and test container building and execution. ## Singularity and MPI Singularity supports MPI fairly well. Since (by default) the network is the same insde and outside the container, the communication between containers usually just works. The more complicated bit is making sure that the container has the right set of MPI libraries. MPI is an open specification, but there are several implementations (OpenMPI, MVAPICH2, and Intel MPI to name three) with some non-overlapping feature sets. If the host and container are running different MPI implementations, or even different versions of the same implementation, hilarity may ensue. The general rule is that you want the version of MPI inside the container to be the same version or newer than the host. You may be thinking that this is not good for the portability of your container, and you are right. Containerizing MPI applications is not terribly difficult with Singularity, but it comes at the cost of additional requirements for the host system. !!! warning Many HPC Systems, like Stampede2 at TACC and Ocelote at UAHPC, have high-speed, low-latency networks that have special drivers. Infiniband, Ares, and OmniPath are three different specs for these types of networks. When running MPI jobs, if the container doesn't have the right libraries, it won't be able to use those special interconnects to communicate between nodes. ## Base Docker images Depending on the system you will use, you may have to build your own MPI enabled Singularity images (to get the versions to match). When running at TACC, there is a set of curated Docker images for use in the FROM line of your own containers. You can see a list of availabe images atAdapted from [CyVerse Container Camp](https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/singularity/hpc.md){target=_blank} (last source update 2022-04-12), CC BY 4.0. Spotted a problem? [Open an issue](https://github.com/UNM-CARC/container-camp/issues){target=_blank}.
---8<--- https://unm-carc.github.io/container-camp/orchestration/kubernetes/ --- title: "Introduction to Kubernetes" description: "Kubernetes concepts and terminology, connecting to an existing K3s cluster with kubectl, and launching pods, deployments, and the dashboard." type: Lesson tags: - Kubernetes - kubectl - K3s generated: by: "claude/opus-5" at: "2026-09-11T00:00:00Z" sources: - id: cyverse-container-camp resource: "https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/orchestration/k8s.md" title: "CyVerse Container Camp: docs/orchestration/k8s.md" author: "team:cyverse" last_modified: "2023-08-15T20:54:48-07:00" --- # Introduction to Kubernetes !!! info "Learning Objectives" * Understand what Kubernetes is and its role in managing containerized applications * Recognize the importance and benefits of using Kubernetes in cloud computing * Identify reasons why you may need to use Kubernetes for your own research or projects * Learn about the existence of Kubernetes clusters and how to leverage them using `kubectl` [:material-kubernetes: Kubernetes](https://kubernetes.io/docs/concepts/overview/what-is-kubernetes/){target=_blank}, or `K8s` for short, is an open-source system designed to automate the deployment, scaling, and management of containerized applications. K8s is the most prevalent platform for managing containerized applications at scale in the realm of cloud computing. If you're considering incorporating containers into your research or project portfolio, it's highly likely you have already interacted with or will interact with a K8s cluster in the future. In this introductory lesson, we'll focus on how to leverage *existing* Kubernetes Clusters using `kubectl`. ??? question ":simple-kubernetes: K8s vs :simple-k3s: K3s" We are going to be using a platform developed by a project named [:simple-rancher: Rancher](https://www.rancher.com/){target=_blank}, called [:simple-k3s: K3s](https://k3s.io/){target=_blank}. K3s is a lightweight certified Kubernetes distribution designed for edge and production workloads. K3s uses the same `kubectl` and `helm` commands as full K8s. We prefer K3s to K8s for our virtual machines and for workshops for the same reason you might prefer to use [Alpine Linux](https://www.alpinelinux.org/){target=_blank} to [Ubuntu Linux](https://ubuntu.com/){target=_blank} in a simple container deployment. ??? question "What is container orchestration?" [Container orchestration](https://www.redhat.com/en/topics/containers/what-is-container-orchestration){target=_blank} is the process of automating the deployment, scaling, management, and coordination of containerized applications. In the context of Kubernetes, container orchestration involves managing the lifecycle of containers within a cluster. This includes tasks such as deploying containers, ensuring high availability, distributing network traffic, scaling applications up or down based on demand, and handling updates seamlessly. ??? question ":material-scale-balance: Why would you want to build your own K8s Cluster? " You're here to learn about containerized applications and container orchestration. We aim to familiarize you with these technologies and help you understand their place in the roadmap of Cloud Native Computing. However, it's crucial to note, ⚠️ __Designing, deploying, and maintaining your own K8s cluster can be a complex and challenging task__ ⚠️ - This is partly because there are numerous existing platforms out there, such as managed K8s services. Additionally, managing K8s requires a dedicated DevOps or software engineering team to ensure your platform operates smoothly. Still interested in building your own cluster? Here are some reasons why you might need to use K8s for your research or project: (1) Your applications consist of multiple services. The K8s API automates the tasks of managing numerous containers and allocating resources. (2) Your work scales dynamically - if your computing needs fluctuate based on workloads, K8s can be useful. Using containers to scale your applications is more efficient than manually launching VMs. (3) You have too many containers to manage - K8s excels at its primary function: managing and maintaining containers. (4) Your domain is transitioning to the cloud. If your field is moving towards being cloud-native, it's crucial to develop workflows in anticipation of this shift. (5) Consistency is key. K8s' declarative state provides a clear description of how everything is managed. !!! info "Kubernetes Terminology" **Kubernetes Cluster**: A collection of nodes that run containerized applications. This is the primary unit of organization in Kubernetes. **kubectl**: The command-line interface used for interacting with Kubernetes clusters. It enables users to manage various aspects of the cluster. **Pod**: The smallest deployable unit in Kubernetes, which can consist of one or more containers that communicate with each other. Pods are run on nodes. **Node**: Typically a virtual machine (VM) that runs Kubernetes components, including the kubelet, kube-proxy, and container runtime. **Kubelet**: The agent that runs on each node, responsible for managing the pods on its respective VM. **Kube-proxy**: A network proxy that runs on each node, used by a Service to handle network communication. **Container runtime**: The software that runs containers within pods. Examples include `Docker`, `containerd` (native to Kubernetes), and `CRI-O`. **Service**: Assigns a network address to an application, including a persistent IP address and a DNS entry within the cluster. A Service also manages load balancing across pods and can dynamically add or remove them. **ReplicaSet**: Allows for the creation of multiple pods simultaneously, ensuring that a specified number of identical pods are running at any given time. **Deployment**: Provides declarative updates for Pods and ReplicaSets, allowing for rolling updates and rollbacks. **Control Plane**: A node that manages worker nodes. It consists of the API server, cluster store, controller manager, and scheduler. **Namespace**: A way to divide cluster resources between multiple users. **Ingress**: An API object that manages external access to the services in a cluster, typically HTTP. **Persistent Volume (PV)**: A piece of storage in the cluster that has been provisioned by an administrator. **Persistent Volume Claim (PVC)**: A request for storage by a user. **ConfigMap**: An API object used to store non-confidential data in key-value pairs. **Secret**: An API object used to store sensitive data, like passwords and keys. !!! question "So, what does a K8s Cluster look like?" In short, this: [{target=_blank}](https://kubernetes.io/docs/concepts/overview/components/) The image above is taken from the official [K8s documentation](https://kubernetes.io/docs/concepts/overview/components/){target=_blank} and depics the relation between each component. A K8s cluster comprises of multiple elemets essentially groupable in 2 subsets: - The **Control Plane** components: - **api** (K8s API): the front end of the control plane, it exposes the Kubernentes API. Command line tool: [`kube-apiserver`](https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/). - **etcd**: a store used for backing cluster data (e.g., cluster cofiguration, state information). Controlled by the [`kube-apiserver`](https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/) tool. - **sched** (Scheduler): the component that watches for newly created Pods with no assigned node. Once a new Pod is detected, a node is assigned. Controlled by [`kube-scheduler`](https://kubernetes.io/docs/reference/command-line-tools-reference/kube-scheduler/). - **c-m** (Controller Manager): controls the [controllers](https://kubernetes.io/docs/concepts/architecture/controller/){target=_blank} processes. Accessible through [`kube-controller-manager`](https://kubernetes.io/docs/reference/command-line-tools-reference/kube-controller-manager/) - **c-c-m** (Cloud Controller Manager): lets you to link your cluster to your provider's API, allowing you to choose what components interact with the external platform and which components interact with the internal cluster. Read more on the cloud controller manager [here](https://kubernetes.io/docs/concepts/architecture/cloud-controller/){target=_blank}. - The **Node** components: - **kubelet**: An agent that runs on each node in the cluster. It makes sure that containers are running in a Pod. [`kubelet`](https://kubernetes.io/docs/reference/command-line-tools-reference/kubelet/) - **k-proxy** (kube-proxy): maintains network rules on nodes. These network rules allow network communication to your Pods from network sessions inside or outside of your cluster. [`kube-proxy`](https://kubernetes.io/docs/reference/command-line-tools-reference/kube-proxy/) !!! question "We keep talking about Pods and Nodes, *what are Pods and Nodes?*" [**Pods**](https://kubernetes.io/docs/concepts/workloads/pods/){target=_blank} [{target=_blank}](https://kubernetes.io/docs/tutorials/kubernetes-basics/explore/explore-intro/) A Pod is the smallest deployable unit in Kubernetes. It represents a single instance of a running process in a cluster and encapsulates one or more closely related containers. Containers within the same Pod share the same network namespace, IP address, and storage volumes, making them suitable for co-located and tightly coupled applications. In the image above, we see 4 different Pods, each having at least one containerized app. Notice how each Pod has its own IP address, and apps within the same Pods share volumes for storage and IP address. [**Nodes**](https://kubernetes.io/docs/concepts/architecture/nodes/){target=_blank} [{target=_blank}](https://kubernetes.io/docs/tutorials/kubernetes-basics/explore/explore-intro/) A Node is a physical or virtual machine that runs containers. Nodes are the worker machines in a Kubernetes cluster where Pods are scheduled and executed. Nodes collectively form the computational resources of the cluster, where containers are scheduled and executed. The interaction between Pods and Nodes forms the core of how Kubernetes manages and distributes workloads within a cluster. In the image, the Node contains different Pods. Notice [`kubelet`](https://kubernetes.io/docs/reference/command-line-tools-reference/kubelet/) and Docker: - [`kubelet`](https://kubernetes.io/docs/reference/command-line-tools-reference/kubelet/) is a component that runs on each Node in a Kubernetes cluster and manages its life cycle, ensuring that the Node is healthy. - Docker provides the runtime environment for containers, whilst K8s manages the orchestration. ## K8s CLI `kubectl` The Kubernetes API uses a command-line tool called `kubectl`. Using K8s does not require you to own or maintain your own cluster. You can use the `kubectl` tool to connect to running clusters and start your containers. ### Install `kubectl` [Official Documentation](https://kubernetes.io/docs/tasks/tools/){target=_blank} `kubectl` is already installed in CodeSpaces. To install on Linux: ``` curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" chmod +x kubectl sudo mv ./kubectl /usr/local/bin/kubectl ``` ### :material-kubernetes: kubeconfig K8s uses YAML files for configuring a cluster. The `config` file is required to make the handshake between the K8s Cluster and external requests, like the one you're making from your local computer or CodeSpace. #### `config` To connect to a running K8s cluster, you need to create a `config` (.yaml) and place it in the `~/.kube/` folder. In our demo [K3s](https://k3s.io/){target=_blank} cluster, the `config` file is maintained in the `/etc/rancher/k3s/k3s.yaml`. You can copy the file to `~/.kube/config` or create a symlink. ??? info " example `config` file" Example of the `~/.kube/config` file which is used for K3s ```{yaml} apiVersion: v1 clusters: - cluster: certificate-authority-data:Adapted from [CyVerse Container Camp](https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/orchestration/k8s.md){target=_blank} (last source update 2023-08-15), CC BY 4.0. Spotted a problem? [Open an issue](https://github.com/UNM-CARC/container-camp/issues){target=_blank}.
---8<--- https://unm-carc.github.io/container-camp/orchestration/kubernetes-advanced/ --- title: "Deploying your own Kubernetes clusters" description: "Deploy Zero to JupyterHub with Helm, and compare lightweight Kubernetes distributions such as minikube, MicroK8s, and K3s." type: Lesson tags: - Kubernetes - Helm - JupyterHub generated: by: "claude/opus-5" at: "2026-09-11T00:00:00Z" sources: - id: cyverse-container-camp resource: "https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/orchestration/advk8s.md" title: "CyVerse Container Camp: docs/orchestration/advk8s.md" author: "team:cyverse" last_modified: "2023-08-16T03:36:46Z" --- # Deploying your own Kubernetes clusters ## Deploying your own Kubernetes clusters We are going to be using Jetstream-2 for this section and will rely on their [documentation for Kubernetes deployment](https://docs.jetstream-cloud.org/general/kubernetes/){target=_blank} ### :material-ship-wheel: Helm [Helm](https://helm.sh/){target=_blank} is the package manager that allows users to define, install, and manage K8s applications using [Helm Charts](https://helm.sh/docs/topics/charts/){target=_blank}, which are packages of pre-configured Kubernetes resources. Similarly to how Docker users can create their own containers and share them collaborators through DockerHub, Helm Charts are built by users and shared through repositories such as [artifacthub](https://artifacthub.io/){target=_blank}. This allows for streamlining deployment and management for complex K8s orchestrations. #### Helm example: [Zero2JupyterHub](https://z2jh.jupyter.org/en/latest/index.html){target=_blank} 1. Install Helm - Users can install Helm on their own machines by following the [official documentation](https://helm.sh/docs/intro/install/){target=_blank}. For this example, we are going to assume that students are using a Unix OS. In that case, the following commands should work: ``` curl https://baltocdn.com/helm/signing.asc | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null sudo apt-get install apt-transport-https --yes echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/helm.gpg] https://baltocdn.com/helm/stable/debian/ all main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list sudo apt-get update sudo apt-get install helm ``` !!! info If you are using the Virtual Machines provided by the CyVerse team, Helm is already installed. 2. Add the JupyterHub Helm repository, which contains the charts needed to deploy JupyterLab. ``` helm repo add jupyterhub https://jupyterhub.github.io/helm-chart/ helm repo update ``` You can check the Helm repository list with `helm repo list`. You should see something like ``` NAME URL jupyterhub https://jupyterhub.github.io/helm-chart/ ``` 3. Obtain the `config.yaml` - One can write their own config file, but these take extensive work and deep understanding of charts. What we can do instead, is to *pull* the values that we need from Jupyterhub and save its output to a file. We can do that with the following command: ``` helm show values jupyterhub/jupyterhub > jupyter.yaml ``` - If you were to look inside this yaml file, you will see a long list of configurable lines. These values need not to be changed to execute the jupyterhub, apart from `secretToken`. - Generate the secret token with `openssl rand -hex 32`, copy the hex string and replace the `secretToken` line from the `jupyter.yml` file using a text editor (e.g., nano or vim). 4. From within the folder, deploy the jupyterhub using the following command (installation should only take a minute): ``` helm install jupyterhub jupyterhub/jupyterhub --values jupyter.yaml ``` 5. Access the JupyterHub through your VM IP address! ??? tip "Uninstalling the chart" To uninstall the chart simply do ``` helm uninstall jupyterhub ``` If you also want to remove the repository do ``` helm repo remove jupyterhub ``` The names can get pretty confusing, please understand that the first is uninstalling the chart named `jupyterhub` and the second the repository named `jupyterhub` (and this is why you want to keep names unique :) ). ## Miniaturized versions of Kubernetes ??? question "Why use mini versions of Kubernetes?" There are multiple projects developing "light-weight" Kubernetes. The justification for these projects being that full Kubernetes deployments with all of its functionality increases the cognitive load and the number of configurations and parameters that you must work with when running containers and clusters, particularly at the edge. Projects that are working on miniaturized versions of K8s: | name | functionality | use cases | |------|---------------|-----------| | [minikube](https://minikube.sigs.k8s.io/docs/start/){target=_blank} | | | | [microK8s](https://microk8s.io/){target=_blank} | | runs fast, self-healing, and highly available Kubernetes clusters | | [K3s](https://k3s.io/){target=_blank} | | runs production-level Kubernetes workloads on low resourced and remotely located IoT and Edge devices | | K3d | lightweight wrapper that runs K3s in a docker container | | ### Install `K3s` Follow the [K3s quick-start guide](https://docs.k3s.io/quick-start){target=_blank}. The camp's K3s pod manifests are in [`examples/k3s`](https://github.com/UNM-CARC/container-camp/tree/main/examples/k3s){target=_blank}, and [`examples/terraform-k3s`](https://github.com/UNM-CARC/container-camp/tree/main/examples/terraform-k3s){target=_blank} provisions a K3s cluster on Jetstream2 with Terraform. ### Install `minikube` [Minikube](https://minikube.sigs.k8s.io/docs/start/){target=_blank} is useful for running K8s on a single node or locally -- its primary use is to teach you how to use K8s. ``` curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64 sudo install minikube-linux-amd64 /usr/local/bin/minikube sudo minikube config set vm-driver none ``` The pod manifest from the camp's minikube demo is in [`examples/minikube`](https://github.com/UNM-CARC/container-camp/tree/main/examples/minikube){target=_blank}. ### Installing `microK8s` See the [MicroK8s getting started guide](https://microk8s.io/docs/getting-started){target=_blank}.Adapted from [CyVerse Container Camp](https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/orchestration/advk8s.md){target=_blank} (last source update 2023-08-16), CC BY 4.0. Spotted a problem? [Open an issue](https://github.com/UNM-CARC/container-camp/issues){target=_blank}.
---8<--- https://unm-carc.github.io/container-camp/orchestration/terraform/ --- title: "Introduction to Terraform" description: "Install Terraform, create OpenStack application credentials and an SSH keypair, and init, apply, and destroy VMs on Jetstream2." type: Lesson tags: - Terraform - OpenStack - Jetstream2 - Infrastructure as Code generated: by: "claude/opus-5" at: "2026-09-11T00:00:00Z" sources: - id: cyverse-container-camp resource: "https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/orchestration/terra.md" title: "CyVerse Container Camp: docs/orchestration/terra.md" author: "team:cyverse" last_modified: "2023-08-10T14:17:31-07:00" --- # Introduction to Terraform [:simple-terraform: Terraform](https://www.terraform.io/){target=_blank} is an open-source infrastructure-as-code (IaC) software tool created by [HashiCorp](https://www.hashicorp.com/){target=_blank}. ??? info "What is Infrastructure-as-Code (IaC)?" "Infrastructure as code (IaC) is the process of managing and provisioning computer data centers through machine-readable definition files, rather than physical hardware configuration or interactive configuration tools." - [:simple-wikipedia: Wikipedia](https://en.wikipedia.org/wiki/Infrastructure_as_code){target=_blank} IaC tools allow you to manage infrastructure with configuration files rather than through a graphical user interface. IaC allows you to build, change, and manage your infrastructure in a safe, consistent, and repeatable way by defining resource configurations that you can version, reuse, and share. -- [:simple-terraform: Terraform Documentation](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/infrastructure-as-code){target=_blank} ## Overview This basic tutorial will guide you through setting up a Terraform project and deploying virtual machines (VMs) as infrastructure on OpenStack Cloud. !!! success "Goals" :material-play: Understand orchestration for deployment to OpenStack cloud (Jetstream2) :material-play: Understand the benefits of Terraform :material-play: Ability to perform basic deployments on OpenStack using Terraform :material-play: Ability to perform provisioning of deployed OpenStack resources through Terraform ??? failure "Things we won't cover" :material-play: OpenStack API :material-play: All of Terraform's features ## Prerequisites * Basic understanding of [:simple-openstack: OpenStack](https://www.openstack.org/){target=_blank} and VMs * Access to an OpenStack cloud (we will use [Jetstream2](https://docs.jetstream-cloud.org/){target=_blank}) * [:simple-terraform: Terraform](https://developer.hashicorp.com/terraform/downloads){target=_blank} installed on your local machine * Optional: request your own allocation to Jetstream2 on [NSF ACCESS-CI](https://allocations.access-ci.org/){target=_blank} !!! success "Outcomes" By the end of this tutorial, you will :material-play: have created SSH keypair :material-play: generated an `*-openrc.sh` file from OpenStack :material-play: started, stopped, and destroyed a Terraform deployment on an OpenStack Cloud ??? info "Terminology" **:material-play: Ansible** - is a suite of software tools that enables infrastructure as code **:material-play: Deploy** - to create a cloud resource or software **:material-play: Infrastructure** - is the collection of hardware and software elements such as computing power, networking, storage, and virtualization resources needed to enable cloud computing **:material-play: Orchestration** - is the automated configuring, coordinating, and managing of computer systems and software **:material-play: Playbook** - are a list of tasks that automatically execute against a host **:material-play: Provision** - making changes to a VM including updating the operating system, installing software, adding configurations **:material-play: Terraform** - is an infrastructure as code tool that lets you build, change, and version cloud and on-prem resources safely and efficiently ### Getting onto :simple-openstack: OpenStack Cloud ??? info "What is OpenStack?" [:simple-openstack: OpenStack](https://www.openstack.org/){target=_blank} is an open source cloud computing infrastructure software project and is one of the three most active open source projects in the world. OpenStack clouds are managed by individuals and institutions on their own bare-metal hardware. [{width=200}](https://allocations.access-ci.org/){target=_blank} If you do not have an account, go to [https://allocations.access-ci.org/](https://allocations.access-ci.org/){target=_blank} and begin the process by requesting an "Explore" start-up allocation. ACCESS is the NSF's management layer for their research computing network (formerly called TerraGrid and XSEDE) which includes high performance computing, high throughput computing, and research clouds like Jetstream2. [{width=200}](https://docs.jetstream-cloud.org/general/access/){target=_blank} Jetstream2 is a public research cloud which uses OpenStack as its management layer. CyVerse is developing a User Interface for Jetstream2 called [CACAO (Cloud Automation & Continuous Analysis Orchestration)](https://cacao.jetstream-cloud.org/help){target=_blank}. Beneath its hood is Terraform. CACAO can also be used from the CLI (which we will [show in a later lesson](https://unm-carc.github.io/container-camp/orchestration/cacao-terraform/)). ## :simple-terraform: Terraform installation [:simple-terraform: Official Documentation](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli#install-terraform){target=_blank} ??? tip "Windows Installation" Download [Terraform](https://developer.hashicorp.com/terraform/downloads){target=_blank} using the appropriate distribution for your OS ??? tip "Mac OS X Installation" Instructions for Mac OS X installation If you're on OS X, you can use [brew](https://brew.sh/){target=_blank} to install with the following commands: ```bash # install terraform -- taken from https://learn.hashicorp.com/tutorials/terraform/install-cli brew tap hashicorp/tap && brew install hashicorp/tap/terraform # install ansible and jq (for processing terraform's statefile into an ansible inventory) brew install ansible jq ``` ??? tip "Linux Installation" Instructions for Ubuntu 22.04 installation ```bash wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor | sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list sudo apt update && sudo apt install terraform ``` Install Ansible & J Query ```bash sudo apt-add-repository ppa:ansible/ansible sudo apt update & sudo apt install -y ansible jq ``` Confirm installation: ```bash terraform ``` Should output: ```bash Usage: terraform [global options]Adapted from [CyVerse Container Camp](https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/orchestration/terra.md){target=_blank} (last source update 2023-08-10), CC BY 4.0. Spotted a problem? [Open an issue](https://github.com/UNM-CARC/container-camp/issues){target=_blank}.
---8<--- https://unm-carc.github.io/container-camp/orchestration/terraform-advanced/ --- title: "Advanced Terraform: managing Docker containers" description: "Practice count, for_each, input validation, preconditions, and dynamic blocks by managing Docker containers with the Terraform Docker provider." type: Lesson tags: - Terraform - Docker - Infrastructure as Code generated: by: "claude/opus-5" at: "2026-09-11T00:00:00Z" sources: - id: cyverse-container-camp resource: "https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/orchestration/advterra.md" title: "CyVerse Container Camp: docs/orchestration/advterra.md" author: "team:cyverse" last_modified: "2023-08-17T12:07:39-07:00" --- # Advanced Terraform: managing Docker containers ## Overview This advanced tutorial will guide you through setting up a Terraform project with [Docker](https://www.docker.com/){target=_blank}. !!! success "Goals" :material-play: Understand how to use registry.terraform.io :material-play: Understand practical terraform advanced language concepts by orchestrating Docker containers :material-play: Understand how to install software and provision multiple VMs :material-play: Understand how Terraform is used to orchestrate VMs together :material-play: Undertand how Terraform is used to create a multi-node JupyterHub ??? failure "Things we won't cover" :material-play: Basic management of Kubernetes clusters (that is a different lesson) :material-play: All of Terraform's features ## Using registry.terraform.io Terraform maintains an active registry site located at [https://registry.terraform.io](https://registry.terraform.io){target=_blank}. This is the Terraform's defacto catalog for - providers - modules - policies (enforcement rules via Terraform Cloud) - runtasks (integrations with other services via Terraform Cloud) Examples of providers: - https://registry.terraform.io/providers/terraform-provider-openstack/openstack/latest - https://registry.terraform.io/providers/kreuzwerker/docker/latest - https://registry.terraform.io/providers/hashicorp/kubernetes/latest ## Prerequisites in doing Terraform exercises * Basic understanding of [:simple-openstack: OpenStack](https://www.openstack.org/){target=_blank} and [:simple-terraform: Terraform](https://terraform-docs.io/){target=_blank} as was covered in the [prior lesson](https://unm-carc.github.io/container-camp/orchestration/terraform/) * Access to an OpenStack cloud (we will use [Jetstream2](https://docs.jetstream-cloud.org/){target=_blank}) * [:simple-terraform: Terraform](https://developer.hashicorp.com/terraform/downloads){target=_blank} installed on your workstation or local machine !!! success "Outcomes" By the end of this tutorial, you will :material-play: have used Terraform to manage Docker containers while learning advanced Terraform concepts ## Using a Terraform to simply manage Docker We will use VMs with Docker installed and learn how to launch containers in a declarative way, rather than using the `docker` command. Once you gain access to your VM, perform the following steps 1. ssh into your VM (alternatively, you can declare use the Docker Terraform provider using ssh access) 2. `git clone https://gitlab.com/stack0/terraform-docker-helloworld.git` 3. `cd terraform-docker-helloworld` 4. Review `input.tf`, `main.tf`, and `terraform.tfvars.example` to get a sense of how to manage a Docker container using Terraform 1. Review the concept of a Terraform `resource` 2. How many resources are created? 3. What is the relationship between the resources? 4. Is there any question that comes to mind about the port property of `docker_container` resource 5. `cp terraform.tfvars.example terraform.tfvars` 6. edit `terraform.tfvars` 1. feel free to edit the `image` or `container_name` with a container you prefer, but if you do, please select a container with a port that you can access 2. if you are using local docker access, use the default value; if you are accessing the docker host remotely, use the string "ssh://myuser@1.2.3.4", where `myuser` is replaced with your vm username and `1.2.3.4` is replaced with your vm's ip address 7. `terraform apply -auto-approve` ??? success "Expected Response" ```bash Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # docker_container.mycontainer will be created + resource "docker_container" "mycontainer" { + attach = false + bridge = (known after apply) + command = (known after apply) + container_logs = (known after apply) + container_read_refresh_timeout_milliseconds = 15000 + entrypoint = (known after apply) + env = (known after apply) + exit_code = (known after apply) + hostname = (known after apply) + id = (known after apply) + image = (known after apply) + init = (known after apply) + ipc_mode = (known after apply) + log_driver = (known after apply) + logs = false + must_run = true + name = "edwins-container" + network_data = (known after apply) + read_only = false + remove_volumes = true + restart = "no" + rm = false + runtime = (known after apply) + security_opts = (known after apply) + shm_size = (known after apply) + start = true + stdin_open = false + stop_signal = (known after apply) + stop_timeout = (known after apply) + tty = false + wait = false + wait_timeout = 60 } # docker_image.mydocker will be created + resource "docker_image" "mydocker" { + id = (known after apply) + image_id = (known after apply) + name = "nginx:latest" + repo_digest = (known after apply) } Plan: 2 to add, 0 to change, 0 to destroy. docker_image.mydocker: Creating... docker_image.mydocker: Creation complete after 3s [id=sha256:89da1fb6dcb964dd35c3f41b7b93ffc35eaf20bc61f2e1335fea710a18424287nginx:latest] docker_container.mycontainer: Creating... docker_container.mycontainer: Creation complete after 1s [id=54f9987cde7fe5b474348ca3e89955c24eb076d6d4ea49aee7e44928f3f8e711] Apply complete! Resources: 2 added, 0 changed, 0 destroyed. ``` 8. `terraform destroy -auto-approve` ## Increasing the number of containers using count We will next update Terraform to create multiple containers of the same image. 1. Modify the `input.tf` with a new variable ``` variable "num_containers" { type = number description = "number, number of containers to launch" default = 1 } ``` 2. Modify the `main.tf` with the following changes (highlighted lines): ```hcl hl_lines="2 4 7 10-14" resource "docker_container" "mycontainer" { count = var.num_containers image = docker_image.mydocker.image_id name = "${format("%s%02d", var.container_name, count.index)}" ports { internal = 80 external = "${8080 + count.index}" # illustrates using count.index to indicate which port to use } } output "docker_containers" { value = keys({ for index, d in docker_container.mycontainer.* : "${format("%s,%s,%s", index, d.name,d.id)}" => d }) } ``` 3. Edit your `terraform.tfvars` to include a new `num_containers` input variable with a value `5` 4. `terraform apply -auto-approve` ??? success "Expected Response" ```bash $ terraform apply -auto-approve Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # docker_container.mycontainer[0] will be created + resource "docker_container" "mycontainer" { + attach = false + bridge = (known after apply) + command = (known after apply) + container_logs = (known after apply) + container_read_refresh_timeout_milliseconds = 15000 + entrypoint = (known after apply) + env = (known after apply) + exit_code = (known after apply) + hostname = (known after apply) + id = (known after apply) + image = (known after apply) + init = (known after apply) + ipc_mode = (known after apply) + log_driver = (known after apply) + logs = false + must_run = true + name = "mycontainer00" + network_data = (known after apply) + read_only = false + remove_volumes = true + restart = "no" + rm = false + runtime = (known after apply) + security_opts = (known after apply) + shm_size = (known after apply) + start = true + stdin_open = false + stop_signal = (known after apply) + stop_timeout = (known after apply) + tty = false + wait = false + wait_timeout = 60 + ports { + external = 8080 + internal = 80 + ip = "0.0.0.0" + protocol = "tcp" } } # docker_container.mycontainer[1] will be created + resource "docker_container" "mycontainer" { + attach = false + bridge = (known after apply) + command = (known after apply) + container_logs = (known after apply) + container_read_refresh_timeout_milliseconds = 15000 + entrypoint = (known after apply) + env = (known after apply) + exit_code = (known after apply) + hostname = (known after apply) + id = (known after apply) + image = (known after apply) + init = (known after apply) + ipc_mode = (known after apply) + log_driver = (known after apply) + logs = false + must_run = true + name = "mycontainer01" + network_data = (known after apply) + read_only = false + remove_volumes = true + restart = "no" + rm = false + runtime = (known after apply) + security_opts = (known after apply) + shm_size = (known after apply) + start = true + stdin_open = false + stop_signal = (known after apply) + stop_timeout = (known after apply) + tty = false + wait = false + wait_timeout = 60 + ports { + external = 8081 + internal = 80 + ip = "0.0.0.0" + protocol = "tcp" } } # docker_container.mycontainer[2] will be created + resource "docker_container" "mycontainer" { + attach = false + bridge = (known after apply) + command = (known after apply) + container_logs = (known after apply) + container_read_refresh_timeout_milliseconds = 15000 + entrypoint = (known after apply) + env = (known after apply) + exit_code = (known after apply) + hostname = (known after apply) + id = (known after apply) + image = (known after apply) + init = (known after apply) + ipc_mode = (known after apply) + log_driver = (known after apply) + logs = false + must_run = true + name = "mycontainer02" + network_data = (known after apply) + read_only = false + remove_volumes = true + restart = "no" + rm = false + runtime = (known after apply) + security_opts = (known after apply) + shm_size = (known after apply) + start = true + stdin_open = false + stop_signal = (known after apply) + stop_timeout = (known after apply) + tty = false + wait = false + wait_timeout = 60 + ports { + external = 8082 + internal = 80 + ip = "0.0.0.0" + protocol = "tcp" } } # docker_container.mycontainer[3] will be created + resource "docker_container" "mycontainer" { + attach = false + bridge = (known after apply) + command = (known after apply) + container_logs = (known after apply) + container_read_refresh_timeout_milliseconds = 15000 + entrypoint = (known after apply) + env = (known after apply) + exit_code = (known after apply) + hostname = (known after apply) + id = (known after apply) + image = (known after apply) + init = (known after apply) + ipc_mode = (known after apply) + log_driver = (known after apply) + logs = false + must_run = true + name = "mycontainer03" + network_data = (known after apply) + read_only = false + remove_volumes = true + restart = "no" + rm = false + runtime = (known after apply) + security_opts = (known after apply) + shm_size = (known after apply) + start = true + stdin_open = false + stop_signal = (known after apply) + stop_timeout = (known after apply) + tty = false + wait = false + wait_timeout = 60 + ports { + external = 8083 + internal = 80 + ip = "0.0.0.0" + protocol = "tcp" } } # docker_container.mycontainer[4] will be created + resource "docker_container" "mycontainer" { + attach = false + bridge = (known after apply) + command = (known after apply) + container_logs = (known after apply) + container_read_refresh_timeout_milliseconds = 15000 + entrypoint = (known after apply) + env = (known after apply) + exit_code = (known after apply) + hostname = (known after apply) + id = (known after apply) + image = (known after apply) + init = (known after apply) + ipc_mode = (known after apply) + log_driver = (known after apply) + logs = false + must_run = true + name = "mycontainer04" + network_data = (known after apply) + read_only = false + remove_volumes = true + restart = "no" + rm = false + runtime = (known after apply) + security_opts = (known after apply) + shm_size = (known after apply) + start = true + stdin_open = false + stop_signal = (known after apply) + stop_timeout = (known after apply) + tty = false + wait = false + wait_timeout = 60 + ports { + external = 8084 + internal = 80 + ip = "0.0.0.0" + protocol = "tcp" } } # docker_image.mydocker will be created + resource "docker_image" "mydocker" { + id = (known after apply) + image_id = (known after apply) + name = "nginx:latest" + repo_digest = (known after apply) } Plan: 6 to add, 0 to change, 0 to destroy. Changes to Outputs: + docker_containers = (known after apply) docker_image.mydocker: Creating... docker_image.mydocker: Creation complete after 3s [id=sha256:89da1fb6dcb964dd35c3f41b7b93ffc35eaf20bc61f2e1335fea710a18424287nginx:latest] docker_container.mycontainer[1]: Creating... docker_container.mycontainer[4]: Creating... docker_container.mycontainer[2]: Creating... docker_container.mycontainer[0]: Creating... docker_container.mycontainer[3]: Creating... docker_container.mycontainer[1]: Creation complete after 1s [id=8c80b558394f62fe004ff156fa1fee06ce723f2b0721cfdfd0f4e64fb6fb28f8] docker_container.mycontainer[3]: Creation complete after 1s [id=c0bc456f847a4e403ee4bcd133f79a21f22b4ec8f94db8793160b28807789e06] docker_container.mycontainer[0]: Creation complete after 1s [id=4ce1d4e0617994ba225701e5e0ef243daa8762968c8d7202be2612ecaeaca064] docker_container.mycontainer[2]: Creation complete after 1s [id=36cf1496a418395408a9d5630c8e1cf8b12fa948ab73fe95a3b1a3d8ce767e99] docker_container.mycontainer[4]: Creation complete after 1s [id=46ab03a8cd2616f20d34058d20b298df8ecc84bf5e8cfaf8013086f21ba1f501] Apply complete! Resources: 6 added, 0 changed, 0 destroyed. Outputs: docker_containers = [ "0,mycontainer00,4ce1d4e0617994ba225701e5e0ef243daa8762968c8d7202be2612ecaeaca064", "1,mycontainer01,8c80b558394f62fe004ff156fa1fee06ce723f2b0721cfdfd0f4e64fb6fb28f8", "2,mycontainer02,36cf1496a418395408a9d5630c8e1cf8b12fa948ab73fe95a3b1a3d8ce767e99", "3,mycontainer03,c0bc456f847a4e403ee4bcd133f79a21f22b4ec8f94db8793160b28807789e06", "4,mycontainer04,46ab03a8cd2616f20d34058d20b298df8ecc84bf5e8cfaf8013086f21ba1f501", ] ``` 5. Notice some pieces of the codes that were introduced 1. What is the `count.index`? 2. What is the format function and why use it? 3. What will index start at? 4. Terraform supports math operations 5. An example of using the keys() method and ad hoc dictionary construction 6. `terraform destroy -auto-approve` ## When resources change outside of Terraform In this exercise we'll discover how to use Terraform to handle change. 1. Use `docker stop` and `docker rm` to stop and delete docker containers 2 and 3. ??? success "Expected Response" ```bash $ docker stop 36cf1496a418395408a9d5630c8e1cf8b12fa948ab73fe95a3b1a3d8ce767e99 c0bc456f847a4e403ee4bcd133f79a21f22b4ec8f94db8793160b28807789e06 36cf1496a418395408a9d5630c8e1cf8b12fa948ab73fe95a3b1a3d8ce767e99 c0bc456f847a4e403ee4bcd133f79a21f22b4ec8f94db8793160b28807789e06 $ docker rm 36cf1496a418395408a9d5630c8e1cf8b12fa948ab73fe95a3b1a3d8ce767e99 c0bc456f847a4e403ee4bcd133f79a21f22b4ec8f94db8793160b28807789e06 36cf1496a418395408a9d5630c8e1cf8b12fa948ab73fe95a3b1a3d8ce767e99 c0bc456f847a4e403ee4bcd133f79a21f22b4ec8f94db8793160b28807789e06 ``` 2. Execute a `terraform show` and count the number of instances in the state 3. Execute a `terraform refresh` ??? success "Expected Response" ```bash $ terraform refresh docker_image.mydocker: Refreshing state... [id=sha256:89da1fb6dcb964dd35c3f41b7b93ffc35eaf20bc61f2e1335fea710a18424287nginx:latest] docker_container.mycontainer[3]: Refreshing state... [id=c0bc456f847a4e403ee4bcd133f79a21f22b4ec8f94db8793160b28807789e06] docker_container.mycontainer[2]: Refreshing state... [id=36cf1496a418395408a9d5630c8e1cf8b12fa948ab73fe95a3b1a3d8ce767e99] docker_container.mycontainer[1]: Refreshing state... [id=8c80b558394f62fe004ff156fa1fee06ce723f2b0721cfdfd0f4e64fb6fb28f8] docker_container.mycontainer[4]: Refreshing state... [id=46ab03a8cd2616f20d34058d20b298df8ecc84bf5e8cfaf8013086f21ba1f501] docker_container.mycontainer[0]: Refreshing state... [id=4ce1d4e0617994ba225701e5e0ef243daa8762968c8d7202be2612ecaeaca064] Outputs: docker_containers = [ "0,mycontainer00,4ce1d4e0617994ba225701e5e0ef243daa8762968c8d7202be2612ecaeaca064", "1,mycontainer01,8c80b558394f62fe004ff156fa1fee06ce723f2b0721cfdfd0f4e64fb6fb28f8", "2,mycontainer02,36cf1496a418395408a9d5630c8e1cf8b12fa948ab73fe95a3b1a3d8ce767e99", "3,mycontainer03,c0bc456f847a4e403ee4bcd133f79a21f22b4ec8f94db8793160b28807789e06", "4,mycontainer04,46ab03a8cd2616f20d34058d20b298df8ecc84bf5e8cfaf8013086f21ba1f501", ] ``` 4. Execute a `terraform show` again and recount the instance in the state. Why doesn't the output variable change? 5. Execute a `terraform apply` (without the `-auto-approve`) and review what will be updated. Once you are satisfied with the changes that will happen, enter `yes` at the prompt. ??? success "Expected Response" ```bash $ terraform apply docker_image.mydocker: Refreshing state... [id=sha256:89da1fb6dcb964dd35c3f41b7b93ffc35eaf20bc61f2e1335fea710a18424287nginx:latest] docker_container.mycontainer[0]: Refreshing state... [id=4ce1d4e0617994ba225701e5e0ef243daa8762968c8d7202be2612ecaeaca064] docker_container.mycontainer[1]: Refreshing state... [id=8c80b558394f62fe004ff156fa1fee06ce723f2b0721cfdfd0f4e64fb6fb28f8] docker_container.mycontainer[4]: Refreshing state... [id=46ab03a8cd2616f20d34058d20b298df8ecc84bf5e8cfaf8013086f21ba1f501] Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # docker_container.mycontainer[2] will be created + resource "docker_container" "mycontainer" { + attach = false + bridge = (known after apply) + command = (known after apply) + container_logs = (known after apply) + container_read_refresh_timeout_milliseconds = 15000 + entrypoint = (known after apply) + env = (known after apply) + exit_code = (known after apply) + hostname = (known after apply) + id = (known after apply) + image = "sha256:89da1fb6dcb964dd35c3f41b7b93ffc35eaf20bc61f2e1335fea710a18424287" + init = (known after apply) + ipc_mode = (known after apply) + log_driver = (known after apply) + logs = false + must_run = true + name = "mycontainer02" + network_data = (known after apply) + read_only = false + remove_volumes = true + restart = "no" + rm = false + runtime = (known after apply) + security_opts = (known after apply) + shm_size = (known after apply) + start = true + stdin_open = false + stop_signal = (known after apply) + stop_timeout = (known after apply) + tty = false + wait = false + wait_timeout = 60 + ports { + external = 8082 + internal = 80 + ip = "0.0.0.0" + protocol = "tcp" } } # docker_container.mycontainer[3] will be created + resource "docker_container" "mycontainer" { + attach = false + bridge = (known after apply) + command = (known after apply) + container_logs = (known after apply) + container_read_refresh_timeout_milliseconds = 15000 + entrypoint = (known after apply) + env = (known after apply) + exit_code = (known after apply) + hostname = (known after apply) + id = (known after apply) + image = "sha256:89da1fb6dcb964dd35c3f41b7b93ffc35eaf20bc61f2e1335fea710a18424287" + init = (known after apply) + ipc_mode = (known after apply) + log_driver = (known after apply) + logs = false + must_run = true + name = "mycontainer03" + network_data = (known after apply) + read_only = false + remove_volumes = true + restart = "no" + rm = false + runtime = (known after apply) + security_opts = (known after apply) + shm_size = (known after apply) + start = true + stdin_open = false + stop_signal = (known after apply) + stop_timeout = (known after apply) + tty = false + wait = false + wait_timeout = 60 + ports { + external = 8083 + internal = 80 + ip = "0.0.0.0" + protocol = "tcp" } } Plan: 2 to add, 0 to change, 0 to destroy. Changes to Outputs: ~ docker_containers = [ - "0,mycontainer00,4ce1d4e0617994ba225701e5e0ef243daa8762968c8d7202be2612ecaeaca064", - "1,mycontainer01,8c80b558394f62fe004ff156fa1fee06ce723f2b0721cfdfd0f4e64fb6fb28f8", - "2,mycontainer02,36cf1496a418395408a9d5630c8e1cf8b12fa948ab73fe95a3b1a3d8ce767e99", - "3,mycontainer03,c0bc456f847a4e403ee4bcd133f79a21f22b4ec8f94db8793160b28807789e06", - "4,mycontainer04,46ab03a8cd2616f20d34058d20b298df8ecc84bf5e8cfaf8013086f21ba1f501", ] -> (known after apply) Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: ``` 6. `terraform destroy -auto-approve` ## Validating your input data Next we will show how to add validation to your inputs 1. In your `input.tf`, add the following variable ```bash variable "port_assignment_list" { type = list(number) description = "list of port assignments, size should = num_containers" default = [] validation { condition = length(var.port_assignment_list) > 0 error_message = "Port assignment not > 0" } } ``` 2. In your `main.tf`, replace your docker_container resource to the following: ```bash resource "docker_container" "mycontainer" { count = var.num_containers image = docker_image.mydocker.image_id name = "${format("%s%02d", var.container_name, count.index)}" ports { internal = 80 external = var.port_assignment_list[count.index] # illustrates using count.index to indicate which port to use } } ``` 3. Add/update the following variables in your `terraform.tfvars` ```bash num_containers=5 port_assignment_list=[] ``` 4. `terraform apply -auto-approve` 5. What do you expect? - Try updating the condition such that length must equal `num_containers` Now, let's figure out how to make our validation slightly more useful 1. If necessary, restore your `input.tf` to include the original validation block ```bash variable "port_assignment_list" { type = list(number) description = "list of port assignments, size should = num_containers" default = [] validation { condition = length(var.port_assignment_list) > 0 error_message = "Port assignment not > 0" } } ``` 2. Update your `main.tf` to include a validation block like the following: ```bash resource "docker_container" "mycontainer" { count = var.num_containers image = docker_image.mydocker.image_id name = "${format("%s%02d", var.container_name, count.index)}" ports { internal = 80 external = var.port_assignment_list[count.index] # illustrates using count.index to indicate which port to use } lifecycle { precondition { condition = length(var.port_assignment_list) == num_containers error_message = "length != num_containers" } } } ``` 3. Update your `terraform.tfvars` with the following settings ```bash num_containers=5 port_assignment_list=[4040,5050,6060,7070] ``` 4. `terraform apply -auto-approve` 5. What do you expect? - Try adding another port (or remove a port) so that `num_containers` and the length of the list is equal 6. `terraform destroy -auto-approve` ## Increasing the number of containers using for_each Next we will create multiple docker containers, but using a different method, `for_each`. 1. copy `input.tf`, `main.tf` from `01c-multiple-containers` (overwrite your existing files) 1. review the differences in `input.tf` 2. review the differences in `main.tf` 2. remove the `num_containers` from your `terraform.tfvars` and add a new variable called `containers_map`, something like ``` containers_map={ cat = 8080, dog = 8090, bird = 9090, cow = 9080, horse = 9070 } ``` 3. `terraform apply -auto-approve` 4. Notice some pieces of the codes that were introduced 1. Notice the declaration of a `map`, the default value, and how to set it in `terraform.tfvars` 2. Map keys do not need quoting. What about values? 3. How would you compare the how ports are configured in between the use of `count` and `for_each` 4. Why might you use `count` and `for_each` 5. `terraform destroy -auto-approve` ## Using dynamic blocks create repeatable nestable blocks in resources Next we will see an example of resource properties that can be repeated 1. Copy `input.tf`, `main.tf` from `02a-using-ports` (overwrite your existing files) 2. Review the differences in `main.tf` 2. Edit `input.tf` with a new variable ```bash variable "container_ports" { type = list(object({ internal=string, external=string })) description = "map(object), port object as defined by https://registry.terraform.io/providers/kreuzwerker/docker/latest/docs/resources/container#nestedblock--ports" default = [] } ``` 3. edit the main.tf with the following `docker_container` resource definition ```bash resource "docker_container" "mycontainer" { image = docker_image.mydocker.image_id name = var.container_name dynamic "ports" { for_each = var.container_ports content { internal = ports.value.internal external = ports.value.external } } } ``` 4. Add the following variable in your `terraform.tfvars` ```bash ports = [ {internal="80", external="8080"}, {internal="90", external="9090"} ] ``` 4. `terraform apply -auto-approve` 5. Verify the ports were added when you use `docker ps` 6. `terraform destroy -auto-approve`Adapted from [CyVerse Container Camp](https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/orchestration/advterra.md){target=_blank} (last source update 2023-08-17), CC BY 4.0. Spotted a problem? [Open an issue](https://github.com/UNM-CARC/container-camp/issues){target=_blank}.
---8<--- https://unm-carc.github.io/container-camp/orchestration/cacao/ --- title: "CyVerse CACAO interface" description: "Log in to CACAO on Jetstream2 with ACCESS, add a cloud credential and SSH key, and launch and open a VM deployment." type: Lesson tags: - CACAO - Jetstream2 - Cloud generated: by: "claude/opus-5" at: "2026-09-11T00:00:00Z" sources: - id: cyverse-container-camp resource: "https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/orchestration/cacao.md" title: "CyVerse Container Camp: docs/orchestration/cacao.md" author: "team:cyverse" last_modified: "2023-08-18T14:54:06-07:00" --- # CyVerse CACAO interface ## Log into CACAO (JS2) To start, you will need an ACCESS user account and have access to a project. Go to [https://cacao.jetstream-cloud.org](https://cacao.jetstream-cloud.org){target=_blank} to log in to CACAO. You will be prompted to authenticate with ACCESS. !!! info You should select "ACCESS CI (XSEDE)" as your identity provider unless you linked your other identities through CILogon. If in doubt, select "ACCESS CI (XSEDE)".  ### Add your Cloud Credential Next, you will need to add cloud computing credentials. For now, the only option is Jetstream 2, but in the future, other providers like Google and AWS will be supported. Click on Credentials on the lefthand menu bar. From the Credentials page, click the "+ Add Credential" button and select "Cloud Credential". Select Jetstream 2 and the project you would like to add. These correspond to Jetstream 2 projects you have access to.  Once your credential is added, it should show up on the Credentials page. ### Add your SSH Key Next, you will need to add a public SSH key. This will allow you to access your VMs via SSH. If you already have an SSH key, you can use that. Otherwise, you can generate a new one. Again, click on the "+ Add Credential" button and this time select "Public SSH Key". Enter a name and paste in your public key.  ### Start a Deployment Next, we will start a deployment onto Jetstream 2. Click on the [:material-rocket-launch: Deployments](https://cacao.jetstream-cloud.org/deployments){target=_blank} tab on the lefthand menu bar. You will see your cloud providers and projects, and if you have multiple providers or projects, you can select them here. You should have Jetstream 2 and a project selected. Next, click the "+ Add Deployment" button. You will see several options for default templates to launch VMs, containers, or whole clusters. We will use the first template to launch a single VM.  You will then name the deployment, select the number of instances and size of instances. For now, stick with the default Featured-AlmaLinux8 image and 1 instance of m3.tiny. You can name it whatever you want. ### Access Deployment Once you have submitted your deployment, you can see it on the Deployments page. It will be in the "Starting" status for a few minutes. Once it is running, you can click on it to see details about it and to access it.  From here, you can click on the icons on the right to access a Web Shell or a Web Desktop (for certain images). You can also pause, shelve, or delete the deployment from here. Try opening the Web Shell, which will bring you to a command line inside your running VM.Adapted from [CyVerse Container Camp](https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/orchestration/cacao.md){target=_blank} (last source update 2023-08-18), CC BY 4.0. Spotted a problem? [Open an issue](https://github.com/UNM-CARC/container-camp/issues){target=_blank}.
---8<--- https://unm-carc.github.io/container-camp/orchestration/cacao-terraform/ --- title: "CACAO Terraform templates on the CLI" description: "Install the CACAO CLI, log in with an ACCESS token, import a Terraform template from Git, and deploy it through the CACAO UI." type: Lesson tags: - CACAO - Terraform - Command Line generated: by: "claude/opus-5" at: "2026-09-11T00:00:00Z" sources: - id: cyverse-container-camp resource: "https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/orchestration/cacao_terra.md" title: "CyVerse Container Camp: docs/orchestration/cacao_terra.md" author: "team:cyverse" last_modified: "2023-08-18T14:54:06-07:00" --- # CACAO Terraform templates on the CLI ## Overview This tutorial will guide you through the process importing a basic terraform template into CACAO. A more detailed version of this tutorial can be found here [https://docs.jetstream-cloud.org/ui/cacao/import_terraform_template/](https://docs.jetstream-cloud.org/ui/cacao/import_terraform_template/){target=_blank} By the end of this tutorial, you will have imported a terraform template into CACAO and deployed it using the UI to confirm that it works. ## Prerequisites * Basic understanding of Terraform, git, and the command line. * An OpenStack environment with access to the API (if you need to validate any terraform changes). * Terraform installed on your local machine or VM server (if you need to validate any terraform changes). * An SSH key pair to access the VM server running Terraform (if you need to validate any terraform changes). ## CyVerse CACAO Browser UI One of CyVerse's test deployments for CACAO will be used for this exercise so that we can import a template using the newer metadata schemas, which will be deployed to production in the very near future. The url for the CACAO site that we will use today is [https://cacao.jetstream-cloud.org](https://cacao.jetstream-cloud.org){target=_blank} Please login to verify that your ACCESS credentials work with the CACAO test site. !!! warning For the workshop, use the "ACCESS CI (XSEDE)" identity provider when you login.  ## Installation of the CyVerse CACAO CLI !!! info If you are using a VM provided by the workshop, the CACAO CLI is already installed. You can skip this installation section. The CyVerse CACAO CLI is a command line tool interact with the CyVerse CACAO API. The first step is the install the cli on your local machine or to a vm. - [Linux download for CACAO CLI](https://gitlab.com/cyverse/cacao/-/package_files/88735155/download){target=_blank} - [Windows download for CACAO CLI](https://gitlab.com/cyverse/cacao/-/package_files/88735261/download){target=_blank} - [MacOS (Intel) download for CACAO CLI](https://gitlab.com/cyverse/cacao/-/package_files/88735323/download){target=_blank} - [MacOS (ARM) download for CACAO CLI](https://gitlab.com/cyverse/cacao/-/package_files/88735401/download){target=_blank} If you're using a VM provided by the workshop or a Linux system, you can use these instructions to install the CACAO CLI. 1. If necessary, obtain a shell or terminal on the Linux system you wish to install the CACAO CLI. 2. Copy link for "Linux download for CACAO CLI" from above. 3. `curl https://gitlab.com/cyverse/cacao/-/package_files/88735155/download --output cacao.zip` 4. `unzip cacao.zip # this will create a file cacao_linux_amd64` 5. `sudo mv cacao_linux_amd64 /usr/local/bin/cacao # optional; otherwise, add it into your path` 6. `chmod +x /usr/local/bin/cacao` ## Reviewing the Terraform template we will use to import We will be using [https://github.com/cyverse/cacao-terraform-hello-world](https://github.com/cyverse/cacao-terraform-hello-world){target=_blank} for this tutorial. You will find a directory called `.cacao` at the root of the template and two files: - `metadata.json`, which contains the CACAO-specific metadata for the template - `ui.json`, which contains the UI-specific hints for the template The `ui.json` file is optional, and if not present, then the CACAO UI will present a default naive rendering of the template. Details about the metadata and ui schemas can be found here: [https://docs.jetstream-cloud.org/ui/cacao/import_terraform_template/](https://docs.jetstream-cloud.org/ui/cacao/import_terraform_template/){target=_blank} !!! warning The UI metadata specification is currently in flux and will likely change in the near future, especially as we get feedback from the community. ### Stop! If you want to fork the template, let's do it now. !!! info This is an optional step. You do not need to fork a template for this part of the tutorial, but you may if you wish. ## Using the CACAO CLI ### Login to CACAO CLI 1. `cacao login --browser` ??? tip "What to do if you encounter a login issue" Sometimes login using the command line will fail -- a typo happens, a copy-n-paste of a token happens, using the wrong api url happens, etc -- and you need to reset your login. To reset your login, you can use the following command: `cacao logout` ???+ success "Expected Response" ```bash linenums="1" hl_lines="4" Please provide address of Cacao API. Format Should be: http://Adapted from [CyVerse Container Camp](https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/orchestration/cacao_terra.md){target=_blank} (last source update 2023-08-18), CC BY 4.0. Spotted a problem? [Open an issue](https://github.com/UNM-CARC/container-camp/issues){target=_blank}.
---8<--- https://unm-carc.github.io/container-camp/orchestration/registry/ --- title: "Managing a private container registry" description: "Options for hosting your own container registry: the open-source Docker Registry server and Harbor, which CyVerse runs." type: Guide tags: - Container Registries - Harbor generated: by: "claude/opus-5" at: "2026-09-11T00:00:00Z" sources: - id: cyverse-container-camp resource: "https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/orchestration/registry.md" title: "CyVerse Container Camp: docs/orchestration/registry.md" author: "team:cyverse" last_modified: "2022-04-26T19:09:59Z" --- # Managing a private container registry If you maintain your own cyberinfrastructure and are pulling many containers per day, you may consider hosting your own registry. ### :material-docker: Docker Registry Server Setting up your own Docker Registry Server [Registry deployment documentation](https://docs.docker.com/registry/deploying/){target=_blank} ### :material-lighthouse-on: Harbor [Harbor](https://goharbor.io/){target=_blank} is for managing container registries with Kubernetes CyVerse manages their own public/private Harbor server for their Discovery Environment. You can authenticate to it using your CyVerse user account. [https://harbor.cyverse.org](https://harbor.cyverse.org){target=_blank} CyVerse featured Docker containers in the Discovery Environment are cached on our Harbor, using a combination of GitHub Actions. [https://github.com/cyverse-vice](https://github.com/cyverse-vice){target=_blank}Adapted from [CyVerse Container Camp](https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/orchestration/registry.md){target=_blank} (last source update 2022-04-26), CC BY 4.0. Spotted a problem? [Open an issue](https://github.com/UNM-CARC/container-camp/issues){target=_blank}.
---8<--- https://unm-carc.github.io/container-camp/cloud/jetstream2/ --- title: "NSF ACCESS and Jetstream2" description: "Create an NSF ACCESS account and connect to the Jetstream2 research cloud through Exosphere, Horizon, CACAO, or the OpenStack CLI." type: Guide tags: - Jetstream2 - ACCESS - Cloud generated: by: "claude/opus-5" at: "2026-09-11T00:00:00Z" sources: - id: cyverse-container-camp resource: "https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/cloud/js2.md" title: "CyVerse Container Camp: docs/cloud/js2.md" author: "team:cyverse" last_modified: "2023-08-15T20:54:48-07:00" --- # NSF ACCESS and Jetstream2 ### **1. Creating an Account on NSF ACCESS-CI** **Step 1:** Navigate to the [ACCESS User Registration](https://identity.access-ci.org/new-user){target=_blank} page. **Step 2:** If you had an XSEDE Portal account, your ACCESS ID is the same. Do not create a new ACCESS ID. Instead, select the “ACCESS CI (XSEDE)” identity provider to log in using your XSEDE account. **Step 3:** If you don't have an XSEDE or ACCESS account, you have two options: - [Register with an existing identity](https://identity.access-ci.org/new-user-federated){target=_blank}: Use an existing University account to simplify the sign-up process. With this option, creating an ACCESS-specific password is optional. - [Register without an existing identity](https://identity.access-ci.org/new-user-direct){target=_blank}: You'll be prompted to enter all your registration info and select an ACCESS-specific password. **Step 4:** During registration, ensure you complete the email verification. You'll receive an email from registry@cilogon.org with a URL to complete your registration. **Note:** Avoid exiting your browser during registration. If you face any issues, please [contact the support team](https://identity.access-ci.org/username-reminder){target=_blank}. --- ### **2. Connecting to Jetstream2** **Step 1:** Visit the [Jetstream2 official End User documentation](https://docs.jetstream-cloud.org/ui/){target=_blank}. **Step 2:** Jetstream2 offers multiple interfaces for creating, deploying, and managing Virtual Machines (VMs) and Storage. Familiarize yourself with the available interfaces: - **Exosphere:** A web-based GUI for VM management. - **Horizon:** Another web-based GUI with a different set of features. - **CACAO:** Still in development, but offers unique capabilities. - **OpenStack CLI:** For those who prefer command-line interfaces. **Step 3:** For beginners, it's recommended to start with the **Exosphere** interface. Here's a brief guide: - **Logging In:** Use your ACCESS-CI credentials. - **Creating an Instance:** Follow the on-screen instructions to deploy a VM. - **Accessing an Exosphere Instance:** Once your VM is up, you can access it directly from the Exosphere dashboard. **Step 4:** Dive deeper into the [Jetstream2 documentation](https://docs.jetstream-cloud.org/ui/){target=_blank} to explore advanced features, troubleshooting guides, and more. --- **Note:** This tutorial provides a quick overview of getting started with NSF ACCESS-CI and Jetstream2. For comprehensive details, always refer to the official documentation provided by both platforms. !!! tip "UNM CARC users" CARC works with national cyberinfrastructure partners, including NSF ACCESS and Jetstream2. See [CARC partner cyberinfrastructure](https://carc.unm.edu/docs/about/partners/){target=_blank} for how UNM researchers can use these resources alongside CARC systems.Adapted from [CyVerse Container Camp](https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/cloud/js2.md){target=_blank} (last source update 2023-08-15), CC BY 4.0. Spotted a problem? [Open an issue](https://github.com/UNM-CARC/container-camp/issues){target=_blank}.
---8<--- https://unm-carc.github.io/container-camp/cloud/openstack/ --- title: "Installing the OpenStack CLI" description: "Install the OpenStack command-line client with pip or Homebrew and verify it before connecting to an OpenStack cloud." type: Guide tags: - OpenStack - Command Line - Cloud generated: by: "claude/opus-5" at: "2026-09-11T00:00:00Z" sources: - id: cyverse-container-camp resource: "https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/cloud/openstack.md" title: "CyVerse Container Camp: docs/cloud/openstack.md" author: "team:cyverse" last_modified: "2023-07-28T08:47:04-07:00" --- # Installing the OpenStack CLI ## Connecting to an Existing OpenStack Cloud ## Option 1: Installing OpenStack CLI using Python Step 1: Prerequisites Ensure you have Python installed on your system. You can download Python from the official website: https://www.python.org/downloads/ Step 2: Install OpenStack CLI Open your terminal or command prompt and execute the following command to install the OpenStack CLI using `pip`: ```bash pip install python-openstackclient ``` Step 3: Verify the Installation To verify that the OpenStack CLI has been successfully installed, run the following command: ```bash openstack --version ``` You should see the version number of the installed OpenStack CLI if the installation was successful. ## Option 2: Installing OpenStack CLI using Homebrew (MacOS) Step 1: Prerequisites Ensure you have Homebrew installed on your MacOS. If you don't have Homebrew, you can install it using the following command in your terminal: ```bash /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" ``` Step 2: Install OpenStack CLI Open your terminal and use Homebrew to install the OpenStack CLI: ```bash brew install openstackclient ``` Step 3: Verify the Installation To verify that the OpenStack CLI has been successfully installed, run the following command: ```bash openstack --version ``` You should see the version number of the installed OpenStack CLI if the installation was successful. ## Conclusion In this installation section, we provided two options for installing the OpenStack CLI. The first option uses Python's `pip` package manager, which is a cross-platform method for installation. The second option is specific to MacOS users and uses Homebrew as the package manager. Choose the option that best suits your system, and once the OpenStack CLI is installed, you can proceed with the main tutorial to connect to an existing OpenStack Cloud. If you already have the OpenStack CLI installed, you can skip this section and move on to the authentication and connection steps in the main tutorial. Happy cloud computing!Adapted from [CyVerse Container Camp](https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/cloud/openstack.md){target=_blank} (last source update 2023-07-28), CC BY 4.0. Spotted a problem? [Open an issue](https://github.com/UNM-CARC/container-camp/issues){target=_blank}.
---8<--- https://unm-carc.github.io/container-camp/cloud/codespaces/ --- title: "GitHub Codespaces" description: "What a development environment is, how to get educational access to GitHub Codespaces and Copilot, and how to start a Codespace." type: Guide tags: - GitHub Codespaces - Development Environments generated: by: "claude/opus-5" at: "2026-09-11T00:00:00Z" sources: - id: cyverse-container-camp resource: "https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/cloud/codespaces.md" title: "CyVerse Container Camp: docs/cloud/codespaces.md" author: "team:cyverse" last_modified: "2023-03-01T09:42:48-07:00" --- # GitHub Codespaces For this workshop, we are working in GitHub [:octicons-codespaces-16: CodeSpaces](https://docs.github.com/en/codespaces){target=_blank}. You will be given access to an organization where CodeSpaces have been enabled for the duration of the workshop. ??? question "What is a 'Development Environment'?" A development environment or "dev environment" for short, is a place where software can be written and tested without impacting users or "production environments" as part of the software lifecycle. Containers-within-containers are another way to think about this paradigm. We create a containerized Integrated Development Environment (IDE) container which has all of our favorite software development tools and visual software (e.g., :material-microsoft-visual-studio-code: VSCode) where we can write our program and try out new package installations. **Terminology:** ***:material-dev-to: Development Environment***: Environment for development tasks such as designing, programming, debugging, etc. ***:material-test-tube-empty: Test Environment***: an environment with the same configuration as the "production environment" where testing can be done without interrupting the main service, also called Q/A or "Quality Assurance Environment". ***:material-test-tube: Staging Environment***: Where the work from testing is merged into the built system before public release. ***:octicons-verified-16: Production Environment***: The environment where users interact with the now-public tools. [:simple-github: GitHub :octicons-codespaces-24: CodeSpaces](https://github.com/features/codespaces){target=_blank} provides you with a fully featured Dev Environment running on [:material-microsoft-azure: Microsoft Azure](https://azure.microsoft.com/en-us){target=_blank}. You have been granted access to [GitHub Education](https://education.github.com/){target=_blank}, and can use CodeSpaces and [:octicons-copilot-24: CoPilot](https://github.com/features/copilot){target=_blank} during the workshop. :octicons-codespaces-24: CodeSpace links with your GitHub account for a seamless experience working on code in a Git repository. :octicons-copilot-24: CoPilot is an AI programmer assistant that can help you write code using comments as prompts. ??? question "How can you get your own educational GitHub & CodeSpaces?" You can gain access to discounted GitHub CodeSpaces by enrolling your GitHub account with [GitHub Education](https://education.github.com/){target=_blank} and then applying the [educator discount](https://education.github.com/discount_requests/teacher_application){target=_blank} to your organizations and repos. Not interested in using GitHub based dev environments? Check out [GitPod](https://www.gitpod.io/){target=_blank} or [GitLab Education](https://about.gitlab.com/solutions/education/){target=_blank} and [Developer Environments](https://docs.gitlab.com/runner/development/){target=_blank} ??? question "How do I use CoPilot?" [Install and enable CoPilot on your VSCode](https://docs.github.com/en/codespaces/codespaces-reference/using-github-copilot-in-github-codespaces){target=_blank} (locally, logging into GitHub, or remotely in CodeSpace). Try adding a comment to the top of a file, like your new `Dockerfile`, wait a few seconds ``` # Create a Dockerfile which uses an Ubuntu 22.04 featured base image, FROM ubuntu:22.04 # then install Python 3.10 and pip, RUN apt-get update \ && apt-get install -y python3.10 python3-pip # and give it an ENTRYPOINT to run Python 3.10 ENTRYPOINT [ "python3.10" ] ``` ## Starting a CodeSpace When a GitHub Organization and Repository have CodeSpaces enabled you will see a "Code" button above the README.md {width="300"} Click on the "Code" button and start a new CodeSpace {width="300"} Select the size of the CodeSpace you want (2-4 cores and 4GB to 8GB of RAM should be plenty for today) {width="300"} Click "Create CodeSpace" You will be taken to a loading screen, and after a few moments (<2 minutes) your browser will change to a VS Code instance in your browser. {width="500"} Notice, the GitHub repository where you initiated the CodeSpace is set as the working directory of the EXPLORER :material-file-document-multiple: in the upper left side of VS Code interface. You're in your Git repo, and are able to work with Python, Docker, Node, or any one of many featured developer tools. Further, you can install any tools you like! {width="600"}Adapted from [CyVerse Container Camp](https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/cloud/codespaces.md){target=_blank} (last source update 2023-03-01), CC BY 4.0. Spotted a problem? [Open an issue](https://github.com/UNM-CARC/container-camp/issues){target=_blank}.
---8<--- https://unm-carc.github.io/container-camp/cloud/aws/ --- title: "Creating an AWS account" description: "Step-by-step sign-up for an Amazon Web Services account, from contact and payment details to choosing a support plan." type: Guide tags: - AWS - Cloud generated: by: "claude/opus-5" at: "2026-09-11T00:00:00Z" sources: - id: cyverse-container-camp resource: "https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/cloud/aws.md" title: "CyVerse Container Camp: docs/cloud/aws.md" author: "team:cyverse" last_modified: "2023-07-28T08:47:04-07:00" --- # Creating an AWS account ## Creating Your First AWS Account ### Introduction This tutorial will guide you through the basics of creating your first AWS account. ### Step 1: Visit the AWS Homepage - Navigate to the [AWS homepage](https://aws.amazon.com/){target=_blank}. - Click on the "Create an AWS Account" button, usually located at the top right corner. ### Step 2: Sign Up - You'll be prompted to enter your email address. If you already have an Amazon account, you can use that email. Otherwise, create a new one. - Choose "I am a new user" and click on "Sign in using our secure server". - Fill in the required details, including your name, email, and desired password. ### Step 3: Contact Information - Choose "Professional" if you're setting this up for your organization or work. Otherwise, choose "Personal". - Fill in the required contact details. Ensure the information is accurate, as AWS might use it for verification purposes. ### Step 4: Payment Information - Enter your credit card details. AWS requires this even for the Free Tier, but you won't be charged unless you exceed the Free Tier limits. ### Step 5: Identity Verification - AWS will call you to verify your identity. Enter the PIN you receive during the call. ### Step 6: Choose a Support Plan - AWS offers various support plans. For starters, you can choose the "Basic Plan" which is free. ### Step 7: Confirmation - Once all steps are completed, you'll receive a confirmation message. Congratulations, you now have an AWS account! ### Step 8: Access AWS Services - After setting up your account, you can access the AWS Management Console to explore various AWS services. --- **Note**: Always set a budget to monitor your usage to avoid unexpected charges. AWS Free Tier offers a generous amount of resources, but exceeding those limits can result in charges.Adapted from [CyVerse Container Camp](https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/cloud/aws.md){target=_blank} (last source update 2023-07-28), CC BY 4.0. Spotted a problem? [Open an issue](https://github.com/UNM-CARC/container-camp/issues){target=_blank}.
---8<--- https://unm-carc.github.io/container-camp/resources/vscode/ --- title: "VS Code for Kubernetes" description: "Set up Visual Studio Code with the Kubernetes and Docker extensions to build, deploy, and monitor applications on a cluster." type: Reference tags: - VS Code - Kubernetes generated: by: "claude/opus-5" at: "2026-09-11T00:00:00Z" sources: - id: cyverse-container-camp resource: "https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/resources/vscode.md" title: "CyVerse Container Camp: docs/resources/vscode.md" author: "team:cyverse" last_modified: "2023-07-28T08:47:04-07:00" --- # VS Code for Kubernetes ## Setting Up VSCode for Kubernetes Management If you're diving into Kubernetes (K8s) for the first time, Visual Studio Code (VSCode) is an excellent platform to manage your pods and clusters. **Why use VSCode for Kubernetes?** VSCode is a developer-friendly Integrated Development Environment (IDE) that offers a plethora of features to streamline the software development process. With its vast marketplace, you can integrate various plugins and extensions, including those tailored for Kubernetes. This flexibility allows you to interact with Kubernetes clusters directly from the VSCode interface, making it a top choice for Kubernetes management. ### **1. Setting Up Your Environment:** - **Prerequisites**: Ensure you have Docker and `kubectl` installed. You can verify their installation by running Docker and `kubectl` commands from the shell. - **Kubernetes Cluster**: You can create a local Kubernetes cluster with `minikube` or use Azure Kubernetes Service (AKS) for an Azure-based cluster. For this tutorial, we'll focus on AKS. Ensure you have an Azure account ready. ### **2. Installing the Kubernetes Extension in VSCode:** - Open VSCode and navigate to the Extensions view (Ctrl+Shift+X). - Search for "kubernetes" and select the Microsoft Kubernetes extension to install. ### **3. Containerizing and Publishing Your Application:** - Use the Microsoft Docker Extension in VSCode to build your project, generate a Docker image, and push it to a container registry. ### **4. Creating and Configuring a Kubernetes Cluster:** - With the Kubernetes extension installed, you'll see KUBERNETES in the VSCode Explorer. - Click on "More" and choose "Create Cluster". - Follow the on-screen instructions to set up your Azure Kubernetes cluster. ### **5. Deploying Your Application:** - The Kubernetes extension in VSCode provides autocompletion and code snippets for the Kubernetes manifest file. - Once your manifest file is ready, open the Command Palette (Ctrl+Shift+P) and run "Kubernetes: Create". This will deploy your application to the Kubernetes cluster. ### **6. Monitoring Your Deployment:** - After deployment, use the Kubernetes extension to check the status of your application. Navigate to Workloads in the Explorer, right-click on Pods, and choose "Get" to see the application's status. ### **7. Additional Extensions for Enhanced Kubernetes Development:** - **YAML**: Provides full YAML support in VSCode, including validation, error detection, and auto-completion. - **Cloud Code**: Developed by Google Cloud, it supports Kubernetes and Cloud Run application development. - **Bridge to Kubernetes**: Allows remote debugging and modification of Kubernetes applications. - **Azure Kubernetes Service**: Manage Kubernetes clusters on Azure. - **OpenShift Connector**: Simplifies building, deploying, and managing applications on Kubernetes or OpenShift. --- **Note:** With the right extensions and tools, VSCode is a powerful platform for managing K8s, making your DevOps tasks smoother and more efficient.Adapted from [CyVerse Container Camp](https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/resources/vscode.md){target=_blank} (last source update 2023-07-28), CC BY 4.0. Spotted a problem? [Open an issue](https://github.com/UNM-CARC/container-camp/issues){target=_blank}.
---8<--- https://unm-carc.github.io/container-camp/resources/docker/ --- title: "Docker resources" description: "Curated links for learning Docker and Docker Compose and for private or self-hosted container registries." type: Reference tags: - Docker - Resources generated: by: "claude/opus-5" at: "2026-09-11T00:00:00Z" sources: - id: cyverse-container-camp resource: "https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/resources/docker.md" title: "CyVerse Container Camp: docs/resources/docker.md" author: "team:cyverse" last_modified: "2022-04-20T15:46:36Z" --- # Docker resources ## Useful Resources related to Docker [:material-sunglasses: Awesome Docker List](https://veggiemonk.github.io/awesome-docker){target=_blank} [:material-docker: Docker Labs](https://github.com/docker/labs){target=_blank} [:material-docker: Docker Community Forums](https://forums.docker.com/){target=_blank} [:material-docker: Docker Hub](https://hub.docker.com/){target=_blank} [:material-docker: Docker Official Documentation](http://docs.docker.com/){target=_blank} [:material-docker: Docker on :material-stack-overflow: StackOverflow](https://stackoverflow.com/questions/tagged/docker){target=_blank} [:material-docker: Official :material-twitter: Twitter](http://twitter.com/docker){target=_blank} [:material-docker: Play With Docker](http://training.play-with-docker.com/){target=_blank} ## Useful Resources related to Private and Self-Hosted Container Registries [:material-lighthouse: Harbor.io](https://goharbor.io/){target=_blank} - Go Harbor Kubernetes Docker registry [NVIDIA GPU Cloud](https://catalog.ngc.nvidia.com/){target=_blank} [BioContainers](https://biocontainers.pro/){target=_blank} ## Useful Resources related to Docker Compose [:material-sunglasses: Awesome Docker Compose](https://github.com/docker/awesome-compose){target=_blank} [:material-docker: Official Documentation](https://docs.docker.com/compose/){target=_blank}Adapted from [CyVerse Container Camp](https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/resources/docker.md){target=_blank} (last source update 2022-04-20), CC BY 4.0. Spotted a problem? [Open an issue](https://github.com/UNM-CARC/container-camp/issues){target=_blank}.
---8<--- https://unm-carc.github.io/container-camp/resources/singularity/ --- title: "Apptainer/Singularity resources" description: "Curated links for Apptainer, SingularityCE, and campus and national HPC container documentation." type: Reference tags: - Apptainer - Singularity - Resources generated: by: "claude/opus-5" at: "2026-09-11T00:00:00Z" sources: - id: cyverse-container-camp resource: "https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/resources/singularity.md" title: "CyVerse Container Camp: docs/resources/singularity.md" author: "team:cyverse" last_modified: "2022-05-16T15:31:18Z" --- # Apptainer/Singularity resources ## Useful Resources related to Apptainer and Singularity [Apptainer documentation](https://apptainer.org/docs/){target=_blank} [SyLabs Singularity Community Edition (CE)](https://sylabs.io/singularity/){target=_blank} [Singularity-HPC](https://singularity-hpc.readthedocs.io/en/latest/){target=_blank} [:material-robot: Autamus Singularity Image Repository](https://singularityhub.github.io/singularity-hpc/){target=_blank} [:material-alpha: Carpentries Singularity Incubator](https://carpentries-incubator.github.io/singularity-introduction/){target=_blank} ## Campus HPC resources for Singularity [OpenScienceGrid Singularity Documentation](https://opensciencegrid.org/docs/worker-node/install-singularity/){target=_blank} [TACC HPC Singularity Introduction](https://containers-at-tacc.readthedocs.io/en/latest/singularity/01.singularity_basics.html){target=_blank} [TACC Containers .pdf](https://frontera-portal.tacc.utexas.edu/user-guide/docs/containers-at-tacc.pdf){target=_blank} [NIH HPC](https://hpc.nih.gov/apps/singularity.html){target=_blank} [University of Arizona Singularity Tutorial](https://public.confluence.arizona.edu/display/UAHPC/Containers){target=_blank}Adapted from [CyVerse Container Camp](https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/resources/singularity.md){target=_blank} (last source update 2022-05-16), CC BY 4.0. Spotted a problem? [Open an issue](https://github.com/UNM-CARC/container-camp/issues){target=_blank}.
---8<--- https://unm-carc.github.io/container-camp/resources/kubernetes/ --- title: "Kubernetes and automation resources" description: "Curated links for Kubernetes, Zero to JupyterHub, lightweight distributions, autoscaling, and Terraform." type: Reference tags: - Kubernetes - Terraform - Resources generated: by: "claude/opus-5" at: "2026-09-11T00:00:00Z" sources: - id: cyverse-container-camp resource: "https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/resources/k8s.md" title: "CyVerse Container Camp: docs/resources/k8s.md" author: "team:cyverse" last_modified: "2022-05-14T16:23:56Z" --- # Kubernetes and automation resources ## Useful resources related to :material-kubernetes: Kubernetes [Zero to JupyterHub with Kubernetes](https://zero-to-jupyterhub.readthedocs.io/en/stable/index.html){target=_blank} [:material-sunglasses: Nubenetes Awesome Kubernetes :material-kubernetes: & Cloud](https://nubenetes.com/){target=_blank} - A curated list of awesome references collected since 2018. Microservices architectures rely on DevOps practices, automation, CI/CD (Continuous Integration & Delivery), and API-focused designs. [:material-sunglasses: Golang awesome Kubernetes tools and resources](https://golangexample.com/a-curated-list-of-awesome-kubernetes-tools-and-resources/){target=_blank} [Pacific Research Platform](https://pacificresearchplatform.org/nautilus/){target=_blank} provides K8s through its [Nautilus Clusters](https://ucsd-prp.gitlab.io/){target=_blank} [Admiralty](https://admiralty.io/){target=_blank} - multi-cluster Kubernetes controller [Rancher (K3s) Lightweight Kubernetes](https://rancher.com/docs/k3s/latest/en/){target=_blank} for Edge, IOT, CI deployments [KEDA](https://keda.sh/){target=_blank} - K8s event driven autoscaler. ## Useful resources related to Automation [Terraform by Hashicorp](https://www.terraform.io/){target=_blank} [:material-sunglasses: Terraform Awesome List](https://www.trackawesomelist.com/shuaibiyy/awesome-terraform/readme){target=_blank}Adapted from [CyVerse Container Camp](https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/resources/k8s.md){target=_blank} (last source update 2022-05-14), CC BY 4.0. Spotted a problem? [Open an issue](https://github.com/UNM-CARC/container-camp/issues){target=_blank}.
---8<--- https://unm-carc.github.io/container-camp/resources/camp-notes/ --- title: "Camp discussion and notes" description: "Questions, answers, and hands-on notes captured during the camps, covering Docker, GitHub Actions, Jetstream2, Singularity, Kubernetes, and CACAO." type: Reference tags: - Workshop - Notes generated: by: "claude/opus-5" at: "2026-09-11T00:00:00Z" sources: - id: cyverse-container-camp resource: "https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/resources/post.md" title: "CyVerse Container Camp: docs/resources/post.md" author: "team:cyverse" last_modified: "2023-07-05T15:13:54+02:00" --- # Camp discussion and notes This section summarizes general notes, discussions and questions raised during both the Basics and Advanced section of Container Camp. !!! warning "Before you Continue" This page is for archival and informative use only, reproduction (such as access to JetStream2 allocations, CodeSpaces) is limited. ## Basics ### Queries *Q: What is an image?* A: A file that lives in the cache on your computer... where 'cache' can be thought of like a desk. It's faster to retrieve a file from your desk than from the filing cabinet *Q: What is a container?* A: It's a virtualized run-time environment which starts from the image. A docker image is what you build. It has all of the software necessary to run the code. The Container is when you "activate" the image, an extra layer where you can work on top of the software you put in the image.  The built image will contain its own OS - it will make no difference where you build your container. When you build an image, you can specify the architecture of the machine you want it to run on. Manage resources for your container by using commands to stop, pause, restart, remove a container. *Q: How do I work with data and containers?* A: Containers do not contain large amounts of data, as these will take space in the writable layer of the container (see above image). Instead, it is suggested to use **Volumes** as a best practice. A Volume is a directory that lives outside of the container that can be *attached* to said container. Once attached, the contents of the directory will be viewable and accessible to the container. In order to attach the volume, one must specify the directory on the computer AND the destination folder, separated by a colon (:). The format is as follows `-vAdapted from [CyVerse Container Camp](https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/docs/resources/post.md){target=_blank} (last source update 2023-07-05), CC BY 4.0. Spotted a problem? [Open an issue](https://github.com/UNM-CARC/container-camp/issues){target=_blank}.
---8<--- https://unm-carc.github.io/container-camp/about/credits/ --- title: "Credits" description: "CyVerse origin of Container Camp, its authors and instructors, NSF funding, the Zenodo DOI, and the CC BY 4.0 attribution." type: Reference tags: - About - Credits - Citation generated: by: "claude/opus-5" at: "2026-09-11T00:00:00Z" sources: - id: cyverse-container-camp resource: "https://github.com/CyVerse-learning-materials/container-camp/blob/1746ee8873bcbed2bd3e62500262a9e9571cff16/LICENSE" title: "CyVerse Container Camp: LICENSE" author: "team:cyverse" - id: zenodo-container-camp-2023 resource: "https://doi.org/10.5281/zenodo.10385227" title: "CyVerse-learning-materials/container-camp: Container Camp 2023 (v23.12)" author: "team:cyverse" --- # Credits ## Origin Container Camp was created by [CyVerse](https://cyverse.org){target=_blank}, the NSF-funded cyberinfrastructure project based at the University of Arizona, and taught as a hands-on workshop from 2019 to 2023. The 2023 edition ran as two paired workshops: **Container Camp Basics** (Docker, Docker Compose, and Singularity) and the **Cloud Native Camp** (Kubernetes, Terraform, and CyVerse CACAO). * Original site: [cc.cyverse.org](https://cc.cyverse.org){target=_blank} (also served as `container-camp.cyverse.org`) * Original repository: [CyVerse-learning-materials/container-camp](https://github.com/CyVerse-learning-materials/container-camp){target=_blank} This site is a UNM Center for Advanced Research Computing (CARC) edition of those materials, migrated from branch `mkdocs` at commit `1746ee8`. Every adapted page links to its exact upstream source file in its frontmatter and in the footer. Pages dropped during the migration (the reStructuredText materials from earlier camps, the 2022 venue logistics page, and empty stubs) remain in the original repository; `MIGRATION.md` in [this site's repository](https://github.com/UNM-CARC/container-camp){target=_blank} maps every old URL to its new location. ## Authors and instructors The Zenodo release of the 2023 materials lists these creators: * Tyson L. Swetnam (University of Arizona, CyVerse) — [ORCID 0000-0002-6639-7181](https://orcid.org/0000-0002-6639-7181){target=_blank} * Michele Cosi (CyVerse / Data Science Institute) — [ORCID 0000-0001-7609-1939](https://orcid.org/0000-0001-7609-1939){target=_blank} * Ryan Bartelme * Edwin Skidmore (University of Arizona, CyVerse) * Mary Margaret Sprinkle (CyVerse) * Amanda Cooksey * Michael Culshaw-Maurer * Mariusz Michalowski The 2023 camps were taught by: * Tyson L. Swetnam — [ORCID 0000-0002-6639-7181](https://orcid.org/0000-0002-6639-7181){target=_blank} * Michele Cosi — [ORCID 0000-0001-7609-1939](https://orcid.org/0000-0001-7609-1939){target=_blank} * Edwin Skidmore * [Carlos Lizárraga-Celaya](https://carloslizarragac.github.io/){target=_blank} — [ORCID 0000-0002-0893-4268](https://orcid.org/0000-0002-0893-4268){target=_blank} Thank you to everyone who taught, wrote, and tested these lessons. ## How to cite Please cite the archived release: > Swetnam, T. L., Cosi, M., Bartelme, R., Skidmore, E., Sprinkle, M. M., > Cooksey, A., Culshaw-Maurer, M., & Michalowski, M. (2023). > *CyVerse-learning-materials/container-camp: Container Camp 2023* (Version > 23.12). Zenodo.