How to use docker compose run command to execute one-off tasks

DockerDockerBeginner
Practice Now

Introduction

In this lab, you will learn how to effectively use the docker compose run command to execute one-off tasks within your Docker Compose services. This is a powerful technique for running administrative commands, debugging, or performing specific operations without starting the full service stack.

We will explore various scenarios, including overriding the default service command, enabling service ports for interaction, manually mapping ports, running commands without starting linked services, and automatically removing the container after execution. By the end of this lab, you will be proficient in using docker compose run for diverse one-off tasks.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL docker(("Docker")) -.-> docker/ContainerOperationsGroup(["Container Operations"]) docker(("Docker")) -.-> docker/ImageOperationsGroup(["Image Operations"]) docker(("Docker")) -.-> docker/NetworkOperationsGroup(["Network Operations"]) docker/ContainerOperationsGroup -.-> docker/run("Run a Container") docker/ContainerOperationsGroup -.-> docker/ps("List Running Containers") docker/ContainerOperationsGroup -.-> docker/stop("Stop Container") docker/ContainerOperationsGroup -.-> docker/rm("Remove Container") docker/ImageOperationsGroup -.-> docker/pull("Pull Image from Repository") docker/NetworkOperationsGroup -.-> docker/network("Manage Networks") subgraph Lab Skills docker/run -.-> lab-555091{{"How to use docker compose run command to execute one-off tasks"}} docker/ps -.-> lab-555091{{"How to use docker compose run command to execute one-off tasks"}} docker/stop -.-> lab-555091{{"How to use docker compose run command to execute one-off tasks"}} docker/rm -.-> lab-555091{{"How to use docker compose run command to execute one-off tasks"}} docker/pull -.-> lab-555091{{"How to use docker compose run command to execute one-off tasks"}} docker/network -.-> lab-555091{{"How to use docker compose run command to execute one-off tasks"}} end

Run a one-off command overriding the service command

In this step, we will learn how to run a one-off command in a Docker container, overriding the default command specified in the Docker image or Dockerfile. This is useful for running administrative tasks, debugging, or executing a specific script within the container environment without starting the main service.

First, let's pull a simple Docker image that we can use for this demonstration. We will use the ubuntu image.

docker pull ubuntu:latest

You should see output indicating that the image is being pulled and downloaded.

Using default tag: latest
latest: Pulling from library/ubuntu
...
Status: Downloaded newer image for ubuntu:latest
docker.io/library/ubuntu:latest

Now, let's run a one-off command in a container based on the ubuntu image. We will use the docker run command with the image name and the command we want to execute. For example, let's run the ls -l / command to list the contents of the root directory in the container.

docker run ubuntu ls -l /

This command will create a new container from the ubuntu image, execute the ls -l / command inside it, and then exit. You should see output similar to this, showing the contents of the root directory:

total 68
drwxr-xr-x   2 root root  4096 Oct 26 00:00 bin
drwxr-xr-x   2 root root  4096 Oct 26 00:00 boot
drwxr-xr-x   5 root root   360 Nov  1 00:00 dev
drwxr-xr-x  19 root root  4096 Nov  1 00:00 etc
drwxr-xr-x   2 root root  4096 Oct 26 00:00 home
drwxr-xr-x   7 root root  4096 Oct 26 00:00 lib
drwxr-xr-x   2 root root  4096 Oct 26 00:00 lib64
drwxr-xr-x   2 root root  4096 Oct 26 00:00 media
drwxr-xr-x   2 root root  4096 Oct 26 00:00 mnt
drwxr-xr-x   2 root root  4096 Oct 26 00:00 opt
drwxr-xr-x   2 root root  4096 Oct 04 14:00 proc
drwx------   2 root root  4096 Oct 26 00:00 root
drwxr-xr-x   2 root root  4096 Oct 26 00:00 run
drwxr-xr-x   2 root root  4096 Oct 26 00:00 sbin
drwxr-xr-x   2 root root  4096 Oct 26 00:00 srv
drwxr-xr-x   2 root root  4096 Oct 26 00:00 sys
drwxrwxrwt   2 root root  4096 Oct 26 00:00 tmp
drwxr-xr-x  11 root root  4096 Oct 26 00:00 usr
drwxr-xr-x  12 root root  4096 Oct 26 00:00 var

In this case, the ubuntu image's default command is typically a shell like /bin/bash. By providing ls -l / after the image name, we are telling Docker to run this specific command instead of the default one.

Let's try another command, for example, pwd to print the current working directory inside the container.

docker run ubuntu pwd

The output should be /, indicating the root directory is the default working directory.

/

This demonstrates how you can easily execute arbitrary commands within a container without needing to interactively enter the container or modify the image's default command.

Run a command with service ports enabled

In this step, we will explore how to run a command in a Docker container while still enabling the ports that the service inside the container is configured to expose. This is useful when you need to run a temporary command for debugging or administration but still want the main service to be accessible from outside the container.

For this demonstration, we will use a simple web server image. Let's pull the nginx image, which is a popular web server.

docker pull nginx:latest

You should see output indicating the image is being pulled.

Using default tag: latest
latest: Pulling from library/nginx
...
Status: Downloaded newer image for nginx:latest
docker.io/library/nginx:latest

The default nginx image is configured to listen on port 80 inside the container. To make this port accessible from our host machine, we need to map a host port to the container port using the -p flag with the docker run command. Let's map host port 8080 to container port 80.

Now, instead of running the default Nginx service, let's run a simple command like echo "Hello from the container" while keeping the port mapping enabled.

docker run -p 8080:80 nginx echo "Hello from the container"

You might expect this to start the Nginx server and then print "Hello from the container". However, when you provide a command after the image name in docker run, that command replaces the default command. So, in this case, the container will run the echo command and then exit. The Nginx server will not be started, even though we specified the port mapping.

The output will simply be:

Hello from the container

And if you try to access http://localhost:8080 in your web browser or using curl, you will find that the connection is refused because the Nginx server is not running.

curl http://localhost:8080

The output will likely be:

curl: (7) Failed to connect to localhost port 8080 after ... connection refused

This illustrates an important point: when you override the default command with docker run <image> <command>, the container will only execute the provided command and will not start the service that the image is designed to run. Therefore, the port mapping, while configured, will not be active because the service listening on that port is not running.

To run a command while the service is running and its ports are enabled, you would typically start the service in the background and then execute your command. However, the docker run command is designed to run a single command and then exit. To achieve the effect of running a command alongside a running service, you would typically use docker exec on a container that is already running the service. We will explore docker exec in a later step.

For the purpose of this step, the key takeaway is understanding that providing a command to docker run overrides the default entrypoint/command, and thus the service configured to run by default will not start, even if port mappings are specified.

Run a command with manual port mapping

In this step, we will continue exploring port mapping with docker run, focusing on how to explicitly specify the host and container ports. While the previous step showed that overriding the default command prevents the service from running, understanding manual port mapping is crucial for when you do want the service to be accessible.

We will continue using the nginx image. As a reminder, the nginx image exposes port 80 inside the container. To make this accessible from our host machine, we use the -p flag followed by host_port:container_port.

Let's run the nginx container and map host port 8081 to container port 80. This time, we will not provide an overriding command, so the default Nginx service will start. We will also run it in detached mode (-d) so it runs in the background.

docker run -d -p 8081:80 nginx

You should see a long string of characters, which is the container ID, indicating that the container has started in detached mode.

<container_id>

Now that the container is running and the port is mapped, you can access the Nginx welcome page from your host machine using curl or a web browser.

curl http://localhost:8081

You should see the HTML content of the default Nginx welcome page. This confirms that the Nginx server is running inside the container and is accessible from your host machine via port 8081.

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
</body>
</html>

This demonstrates how manual port mapping allows you to control which host port is used to access a service running on a specific port inside a container. This is essential for avoiding port conflicts on your host machine and for making containerized services accessible.

To stop the running container, you can use the docker stop command followed by the container ID or name. You can find the container ID by running docker ps.

docker ps

This will show you a list of running containers. Find the container ID for the nginx container you just started.

CONTAINER ID   IMAGE     COMMAND                  CREATED          STATUS          PORTS                  NAMES
<container_id>   nginx     "nginx -g 'daemon off"   About a minute ago   Up About a minute   0.0.0.0:8081->80/tcp   <container_name>

Now, stop the container using its ID. Replace <container_id> with the actual ID from your output.

docker stop <container_id>

The container ID will be printed again, confirming that the container has been stopped.

<container_id>

If you try to access http://localhost:8081 again after stopping the container, the connection will be refused.

Run a command without starting linked services

In this step, we will learn how to run a command in a Docker container that is part of a multi-container application, without starting the other linked services. This is particularly useful for running database migrations, setup scripts, or debugging commands in one service without needing to bring up the entire application stack.

While Docker Compose is the standard tool for managing multi-container applications and has features for running one-off commands on specific services, we will demonstrate the underlying Docker concepts here. Since Docker Compose is not pre-installed in this environment, we will focus on using the docker run command with networking.

Let's simulate a simple scenario with two containers: a web application and a database. We'll use a generic ubuntu image to represent our web application and a postgres image for the database.

First, pull the postgres image:

docker pull postgres:latest

You should see output indicating the image is being pulled.

Using default tag: latest
latest: Pulling from library/postgres
...
Status: Downloaded newer image for postgres:latest
docker.io/library/postgres:latest

Now, let's create a Docker network so our containers can communicate with each other by name.

docker network create my-app-network

You should see the network ID printed.

<network_id>

Next, let's run the postgres container and connect it to our network. We'll also set a password for the PostgreSQL user.

docker run -d --network my-app-network --name my-database -e POSTGRES_PASSWORD=mypassword postgres

You should see the container ID printed, indicating the database container is running in the background.

<container_id>

Now, imagine our "web application" container needs to run a command that interacts with the database, like a database migration script. Normally, if we were using Docker Compose, we could run a command on the web service and Docker Compose would handle the network setup and linking.

Using just docker run, if we were to run the web application container and it tried to connect to my-database, it would typically need to be on the same network.

Let's run a command in an ubuntu container connected to the same network, simulating a command that might interact with the database. We'll just try to ping the database container by its name (my-database).

docker run --network my-app-network ubuntu ping -c 4 my-database

This command will:

  1. Create a new container from the ubuntu image.
  2. Connect it to the my-app-network.
  3. Run the ping -c 4 my-database command inside the container.

Because the ubuntu container is on the same network as the my-database container, it can resolve the name my-database to the database container's IP address and ping it.

You should see output showing the ping requests and responses:

PING my-database (172.18.0.2) 56(84) bytes of data.
64 bytes from my-database.my-app-network (172.18.0.2): icmp_seq=1 ttl=64 time=0.050 ms
64 bytes from my-database.my-app-network (172.18.0.2): icmp_seq=2 ttl=64 time=0.054 ms
64 bytes from my-database.my-app-network (172.18.0.2): icmp_seq=3 ttl=64 time=0.054 ms
64 bytes from my-database.my-app-network (172.18.0.2): icmp_seq=4 ttl=64 time=0.054 ms

--- my-database ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3060ms
rtt min/avg/max/mdev = 0.050/0.053/0.054/0.001 ms

This demonstrates that you can run a one-off command in a container and have it interact with other containers on the same network, without needing to start the default service of the container running the command (in this case, the ubuntu container doesn't have a typical "service"). The key is connecting the container running the command to the same network as the services it needs to interact with.

Finally, let's clean up the running database container and the network.

docker stop my-database
my-database
docker rm my-database
my-database
docker network rm my-app-network
my-app-network

Run a command and automatically remove the container

In this step, we will learn how to run a command in a Docker container and automatically remove the container once the command finishes. This is very useful for one-off tasks, scripts, or jobs where you don't need the container to persist after its execution is complete. Automatically removing containers helps keep your system clean and avoids accumulating stopped containers.

We will use the ubuntu image again for this demonstration. We will run a simple command, like printing a message and then exiting. To automatically remove the container after the command finishes, we use the --rm flag with the docker run command.

Let's run a container with the --rm flag and execute the echo "This container will be removed automatically" command.

docker run --rm ubuntu echo "This container will be removed automatically"

This command will:

  1. Create a new container from the ubuntu image.
  2. Run the echo "This container will be removed automatically" command inside the container.
  3. Once the echo command finishes, the container will automatically be removed.

You should see the output of the echo command:

This container will be removed automatically

After the command finishes, the container is stopped and removed. To verify that the container was removed, you can use the docker ps -a command, which lists all containers, including stopped ones.

docker ps -a

You should not see the container that just ran in the list. If you ran the command without the --rm flag, the container would still appear in the output with a status of "Exited".

Let's try another example. We'll run a command that pauses for a few seconds using sleep and then exits, again with the --rm flag.

docker run --rm ubuntu sh -c "echo 'Starting sleep...'; sleep 5; echo 'Sleep finished.'"

This command uses sh -c to execute a simple script that prints messages before and after sleeping for 5 seconds.

You will see the first message immediately:

Starting sleep...

Then, after about 5 seconds, you will see the second message:

Sleep finished.

Once the script finishes, the container will be automatically removed. You can again verify this with docker ps -a.

docker ps -a

The container that ran the sleep command should not be in the list of containers.

Using the --rm flag is a good practice for containers that are designed to run a specific task and then exit, as it helps manage disk space and keeps your list of containers clean.

Summary

In this lab, we learned how to use the docker compose run command to execute one-off tasks within a Docker Compose service. We started by understanding how to override the default command defined in a service's configuration, allowing us to run arbitrary commands for administrative or debugging purposes.

We then explored how to manage network connectivity for these one-off tasks, specifically by enabling service ports and manually mapping ports. Finally, we learned how to control dependencies by running a command without starting linked services and how to automatically remove the container after the command completes, ensuring a clean environment.