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 .iso file for a virtual machine, but much lighter and more efficient. When you download nginx: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.

flowchart TD A[Image] --> B{container run}; B --> C[Container Created]; C --> D[Container Running]; D --> E{container stop}; E --> F[Container Stopped/Exited]; F --> G{container rm}; G --> H[Container Removed]; D -->|\1| F;

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 to localhost:8080 on your Mac will be sent to port 80 inside 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. latest usually 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. (ps stands 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.0 means 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 ID printed 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-webserver before the image name.
  • Run container ps again. You’ll see my-nginx-webserver under the NAMES column. 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:

CommandDescription
container runCreates and starts a new container from an image.
container psLists currently running containers.
container ps -aLists all containers (running, stopped, exited).
container stopStops one or more running containers.
container rmRemoves one or more stopped/exited containers.
-p HOST:CONTAINERMaps a port from your Mac to a port inside the container.
-dRuns 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:

  1. Run the alpine/git image.
  2. Execute the command git --version inside the container.
  3. Exit immediately after showing the Git version.
  4. You should not use the -d flag, so you can see the output directly.
  5. After it exits, list all containers to observe its Exited state.
  6. 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 run can execute a single command and then automatically exit if that’s all the container is designed to do.
  • You practiced using container ps -a to 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:

  1. “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 port 8080 on your Mac.
    • Error Message: Something like Error: port is already allocated or Error: failed to create port mapping: address already in use.
    • Solution:
      • Change the host port: Use -p 8081:80 instead.
      • Find and stop the conflicting process:
        • On macOS, you can use lsof -i :8080 to see what’s using the port.
        • If it’s another container, use container ps to find it, then container stop <ID>.
  2. “Container not found” for stop or rm:

    • 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 ID or NAMES using container ps -a. Copy-pasting is your friend!
      • Remember that container ps only shows running containers. If it’s stopped, you need container ps -a.
  3. Forgetting -d and tying up your terminal:

    • Scenario: You run a long-running service (like Nginx) without the -d flag, 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 ps to find its ID, and then container stop <ID>.
  4. Confusing Images with Containers:

    • Scenario: You try to rm an image using container rm nginx:latest.
    • Error Message: Error: No such container: nginx:latest (or similar, indicating it’s not a container ID/name).
    • Solution: Remember, rm is for containers. Images are removed with container rmi (for “remove image”), which we’ll cover in a later chapter. For now, just know they are distinct.

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, and Removed.
  • container run: The command to launch a new container from an image.
    • Use -p HOST_PORT:CONTAINER_PORT for port mapping.
    • Use -d to run in detached (background) mode.
    • Use --name to assign a memorable name.
  • 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


This page is AI-assisted and reviewed. It references official documentation and recognized resources where relevant.