Introduction
Welcome back, future container maestro! In the previous chapters, we set up Apple’s powerful new tools for running Linux containers directly on your Mac. You’re now equipped with the container CLI, the gateway to a world of efficient, isolated development environments.
This chapter is where the real fun begins. We’ll dive hands-on into the most fundamental operations: running new containers, gracefully stopping them, and tidying up by removing them. Think of it as learning to drive a car – you’ll master how to start it, park it, and even take it to the junkyard (just kidding, we’re very eco-friendly here!).
By the end of this chapter, you’ll not only be able to perform these actions but also understand why and how they work, giving you a solid foundation for more advanced container workflows. Get ready to launch your first native Linux container on macOS!
Core Concepts: Understanding Container Life
Before we start typing commands, let’s take a moment to understand the key ideas behind these operations. This will make every command you run much more meaningful.
What’s the Difference: Image vs. Container?
This is perhaps the most crucial distinction in containerization. You might hear these terms used interchangeably in casual conversation, but technically, they’re very different:
- Image: Think of an image as a blueprint, a template, or a recipe. It’s a static, immutable package that contains everything needed to run an application: the code, a runtime, system tools, libraries, and settings. It’s like a
.isofile for a virtual machine, but much lighter and more efficient. When you downloadnginx:latest, you’re getting an image. - Container: A container is a running instance of an image. When you “run” an image, the container engine takes that blueprint and creates a live, isolated environment based on it. It’s the actual, active “thing” that’s doing work. You can run multiple containers from the same image, each completely isolated from the others. Each container has its own filesystem, network interfaces, and process space.
So, to recap: Images are inert blueprints; containers are live, running instances of those blueprints.
The Container Lifecycle: A Quick Tour
Containers, like any process, go through various states. Understanding these helps you manage them effectively:
- Created: You’ve asked the system to prepare a container from an image, but it hasn’t started running yet.
- Running: The container is actively executing its primary process. This is where your application lives!
- Paused: (Less common for basic operations) The container’s processes are temporarily suspended.
- Stopped/Exited: The container’s primary process has finished or been terminated. It’s no longer running, but its state and resources (like disk space) still exist.
- Removed: The container instance and all its associated resources have been completely deleted from the system.
We’ll primarily focus on Running, Stopped, and Removed states in this chapter.
Figure 4.1: Simplified Container Lifecycle Diagram
Port Mapping: Connecting the Inside to the Outside
Imagine you have a web server running inside your container. This server might be listening for requests on port 80 (the standard HTTP port) within the container’s isolated network. But how do you access it from your Mac’s web browser?
This is where port mapping comes in. You tell the container tool: “Hey, take traffic coming into port 8080 on my Mac, and forward it to port 80 inside this specific container.”
The syntax usually looks like -p HOST_PORT:CONTAINER_PORT.
HOST_PORT: The port number on your macOS machine that you want to use.CONTAINER_PORT: The port number that the application inside the container is listening on.
Without port mapping, your containerized application might be running perfectly, but you wouldn’t be able to reach it from your host machine!
Step-by-Step Implementation: Your First Container
Let’s get practical! We’ll start by running a simple Nginx web server. Nginx is a popular, lightweight web server, perfect for demonstrating container operations.
First, let’s make sure you have the apple/container CLI installed and working from Chapter 3. If you haven’t, please revisit the setup instructions.
Step 1: Running Your First Container
We’ll use the container run command. This command does a lot of work for you: if the image isn’t already on your machine, it will automatically pull (download) it first.
Open your terminal application.
container run -p 8080:80 nginx:latest
Let’s break down this command, piece by piece:
container run: This is the command to create and start a new container from an image.-p 8080:80: This is our port mapping!8080: This is the port on your Mac that you’ll use to access the web server.80: This is the standard HTTP port that the Nginx server inside the container listens on. So, traffic tolocalhost:8080on your Mac will be sent to port80inside the Nginx container.
nginx:latest: This specifies the image we want to use.nginx: The name of the image (the Nginx web server).:latest: The tag of the image.latestusually refers to the most recently released stable version.
When you run this, you’ll see some output from Nginx, like access logs. This means the container is running and its output is being streamed directly to your terminal.
# Expected (simplified) output, actual output may vary slightly
Unable to find image 'nginx:latest' locally
latest: Pulling from library/nginx
... (download progress) ...
Digest: sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Status: Downloaded newer image for nginx:latest
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to run profile scripts
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: /etc/nginx/conf.d/default.conf is not a file or does not exist
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
2026/02/25 10:00:00 [notice] 1#1:F: using the "epoll" event method
2026/02/25 10:00:00 [notice] 1#1:F: nginx/1.25.3
2026/02/25 10:00:00 [notice] 1#1:F: built by gcc 12.2.1 20220924 (Debian 12.2.0-14)
2026/02/25 10:00:00 [notice] 1#1:F: OS: Linux 6.1.0-17-amd64
2026/02/25 10:00:00 [notice] 1#1:F: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2026/02/25 10:00:00 [notice] 1#1:F: start worker processes
2026/02/25 10:00:00 [notice] 1#1:F: start worker process 29
What to Observe:
- You’ll likely see a message about the image being pulled if it’s your first time.
- Then, you’ll see messages from Nginx indicating it’s starting up and running.
- Your terminal is now “attached” to the container’s output. You can’t type new commands in this terminal window until the container stops.
Now, open your web browser and navigate to http://localhost:8080. You should see the Nginx welcome page! Congratulations, you’re serving a web page from a Linux container running natively on your Mac!
Step 2: Listing Running Containers (container ps)
While your Nginx container is still running in the first terminal window, open a new terminal window. We need another terminal because the first one is busy displaying the container’s output.
In the new terminal, type:
container ps
container ps: This command lists all currently running containers. (psstands for “process status”, a nod to traditional Unix commands).
You should see output similar to this:
# Expected output
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
abcdef123456 nginx:latest "nginx -g 'daemon of…" 10 seconds ago Up 9 seconds 0.0.0.0:8080->80/tcp amazing_fermi
What to Observe:
CONTAINER ID: A unique, shortened ID for your container. You’ll often use this to refer to specific containers.IMAGE: The name and tag of the image the container was built from.COMMAND: The command that the container is executing.CREATED: How long ago the container was started.STATUS: The current state of the container (e.g.,Up X seconds).PORTS: The port mapping we defined (0.0.0.0:8080->80/tcp).0.0.0.0means it’s accessible from any network interface on your host.NAMES: A randomly generated name for your container. We’ll learn how to set custom names soon!
Step 3: Stopping a Running Container (container stop)
To stop a container, we use the container stop command, followed by either the CONTAINER ID or the NAMES of the container. Using the CONTAINER ID is generally more reliable as names can sometimes conflict if you’re not careful.
From your second terminal window (where you ran container ps), copy the CONTAINER ID from the container ps output. It’s the first column, a string of characters like abcdef123456.
container stop <YOUR_CONTAINER_ID_HERE>
Replace <YOUR_CONTAINER_ID_HERE> with the actual ID you copied. For example:
container stop abcdef123456
You’ll see the container ID printed back, confirming it’s stopped. Now, go back to your first terminal window where Nginx was running. You’ll notice the output has stopped, and you’ll get your command prompt back.
Try refreshing http://localhost:8080 in your browser. You should now see that the site is unreachable, because the container is no longer running.
Step 4: Listing All Containers (Running and Stopped) (container ps -a)
After stopping the Nginx container, if you run container ps again in your second terminal, you’ll notice it shows no output. This is because ps only lists running containers.
To see all containers, including those that have stopped or exited, we use the -a (for “all”) flag:
container ps -a
Now you should see your Nginx container listed, but its STATUS will be Exited (0) X seconds ago. The (0) typically means it exited cleanly.
# Expected output
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
abcdef123456 nginx:latest "nginx -g 'daemon of…" 2 minutes ago Exited (0) 10 seconds ago amazing_fermi
Step 5: Removing a Container (container rm)
A stopped container still consumes some disk space and retains its configuration. To fully clean it up, you need to remove it. Remember, this removes the container instance, not the image. The nginx:latest image will still be on your system, ready to launch new containers.
Just like with stop, you use the container’s ID or name:
container rm <YOUR_CONTAINER_ID_HERE>
Again, replace <YOUR_CONTAINER_ID_HERE> with the actual ID.
container rm abcdef123456
You’ll see the container ID printed back. Now, if you run container ps -a again, your Nginx container should be completely gone!
Step 6: Running in Detached Mode (-d)
Attaching your terminal to the container’s output is great for debugging, but it’s not ideal for long-running services. What if you want to close your terminal and have the container keep running in the background?
That’s where detached mode comes in, using the -d flag.
Let’s run Nginx again, but this time in detached mode:
container run -d -p 8080:80 nginx:latest
What to Observe:
- Instead of seeing Nginx logs, you’ll immediately get a long
CONTAINER IDprinted to your terminal, and your command prompt will return. This means the container has started in the background. - You can now run other commands in this same terminal window.
- Verify it’s running with
container ps.
Go ahead and check http://localhost:8080 in your browser. It should still be serving the Nginx welcome page!
Step 7: Assigning Custom Names (--name)
Those randomly generated names like amazing_fermi are fun, but not very practical for managing many containers. You can assign your own meaningful names using the --name flag. This makes it much easier to refer to your containers later.
First, stop and remove your currently running Nginx container (the one without a custom name):
# Find its ID first
container ps
# Then stop and remove it
container stop <ID>
container rm <ID>
Now, let’s run Nginx again, this time with a custom name:
container run -d --name my-nginx-webserver -p 8080:80 nginx:latest
What to Observe:
- We’ve added
--name my-nginx-webserverbefore the image name. - Run
container psagain. You’ll seemy-nginx-webserverunder theNAMEScolumn. Much clearer, right?
Now you can stop and remove it using its custom name, which is often more convenient than copying the ID:
container stop my-nginx-webserver
container rm my-nginx-webserver
Recap of Commands and Flags:
| Command | Description |
|---|---|
container run | Creates and starts a new container from an image. |
container ps | Lists currently running containers. |
container ps -a | Lists all containers (running, stopped, exited). |
container stop | Stops one or more running containers. |
container rm | Removes one or more stopped/exited containers. |
-p HOST:CONTAINER | Maps a port from your Mac to a port inside the container. |
-d | Runs the container in detached mode (in the background). |
--name <NAME> | Assigns a custom name to your container. |
Mini-Challenge: The Self-Exiting Container
Alright, time to apply what you’ve learned!
Challenge:
Your task is to run a temporary container that simply executes a command and then exits. We’ll use the alpine/git image, which includes the git command-line tool. Your container should:
- Run the
alpine/gitimage. - Execute the command
git --versioninside the container. - Exit immediately after showing the Git version.
- You should not use the
-dflag, so you can see the output directly. - After it exits, list all containers to observe its
Exitedstate. - Finally, remove the container.
Hint: The command to run inside the container comes after the image name in container run.
Take a moment, try it out in your terminal. Don’t worry if it takes a few tries!
Click for Solution (but try it yourself first!)
# 1. Run the container and execute the command
container run alpine/git git --version
# Expected output (will vary by Git version):
# Unable to find image 'alpine/git:latest' locally
# latest: Pulling from alpine/git
# ... (download progress) ...
# git version 2.43.0
# You'll immediately get your prompt back because it exited.
# 2. List all containers to see its exited state
container ps -a
# You should see an entry for alpine/git with a STATUS like "Exited (0) X seconds ago"
# 3. Remove the container (replace <ID> with the actual ID)
container rm <ID_of_alpine/git_container>
# Verify it's gone
container ps -a
What to Observe/Learn:
- You saw how
container runcan execute a single command and then automatically exit if that’s all the container is designed to do. - You practiced using
container ps -ato see containers that are no longer running. - You successfully cleaned up a temporary container. This pattern is very common for build tasks, scripts, or one-off operations.
Common Pitfalls & Troubleshooting
Even with simple commands, you might run into a few common issues. Here’s how to tackle them:
“Port already in use” Error:
- Scenario: You try to run a container with
-p 8080:80, but you already have another application (or another container!) using port8080on your Mac. - Error Message: Something like
Error: port is already allocatedorError: failed to create port mapping: address already in use. - Solution:
- Change the host port: Use
-p 8081:80instead. - Find and stop the conflicting process:
- On macOS, you can use
lsof -i :8080to see what’s using the port. - If it’s another container, use
container psto find it, thencontainer stop <ID>.
- On macOS, you can use
- Change the host port: Use
- Scenario: You try to run a container with
“Container not found” for
stoporrm:- Scenario: You’re trying to stop or remove a container, but you’ve typed the wrong ID or name.
- Error Message:
Error: No such container: <your_typo_id> - Solution:
- Double-check the
CONTAINER IDorNAMESusingcontainer ps -a. Copy-pasting is your friend! - Remember that
container psonly shows running containers. If it’s stopped, you needcontainer ps -a.
- Double-check the
Forgetting
-dand tying up your terminal:- Scenario: You run a long-running service (like Nginx) without the
-dflag, and your terminal becomes unusable. - Solution:
- In the terminal where the container is running, press
Ctrl+C. This will usually send a signal to the container’s main process, causing it to stop and returning your prompt. - Alternatively, open a new terminal, use
container psto find its ID, and thencontainer stop <ID>.
- In the terminal where the container is running, press
- Scenario: You run a long-running service (like Nginx) without the
Confusing Images with Containers:
- Scenario: You try to
rman image usingcontainer rm nginx:latest. - Error Message:
Error: No such container: nginx:latest(or similar, indicating it’s not a container ID/name). - Solution: Remember,
rmis for containers. Images are removed withcontainer rmi(for “remove image”), which we’ll cover in a later chapter. For now, just know they are distinct.
- Scenario: You try to
Summary
Phew! You’ve just mastered the fundamental building blocks of container management with Apple’s container CLI. Let’s quickly recap what you’ve learned:
- Images vs. Containers: Images are blueprints, containers are live instances.
- Container Lifecycle: Containers move through states like
Created,Running,Stopped, andRemoved. container run: The command to launch a new container from an image.- Use
-p HOST_PORT:CONTAINER_PORTfor port mapping. - Use
-dto run in detached (background) mode. - Use
--nameto assign a memorable name.
- Use
container ps&container ps -a: Commands to list running containers and all containers (including stopped ones), respectively.container stop: Halts a running container.container rm: Permanently deletes a stopped container instance.
You’re now comfortable launching, inspecting, stopping, and removing containers. This is a huge step towards leveraging the power of containerization on your Mac!
What’s Next?
In the next chapter, we’ll take things up a notch. Instead of just running pre-built images, you’ll learn how to create your own custom container images using Dockerfiles and the container build command. Get ready to truly package your applications for the container world!
References
- Apple Container GitHub Repository
- Apple Container Documentation - How-To Guide
- Apple Container Documentation - Command Reference
- Nginx Official Docker Image on Docker Hub
- Alpine Git Official Docker Image on Docker Hub
This page is AI-assisted and reviewed. It references official documentation and recognized resources where relevant.