1. Overview
A Docker container runs a process, application, or sometimes just a script or a command, to perform the task it’s designed for.
Each container stops and exits once it doesn’t have any process or script running within the container. Some containers run continuously by default until a user chooses to stop them, like a container for a MySQL database, a Spring Boot web application, or an SMTP mail server. Sometimes, though, we need to make a container stay running even after its primary task has been completed, like an Ubuntu container.
In this article, we’ll learn how to make it happen with Docker Compose.
2. Docker Compose Setup
Docker Compose is a tool we use to define and run multi-container services. The only prerequisite is to install Docker (Docker Server, Docker Client, and Docker Compose) on a supported OS platform (we’ll use Linux Ubuntu in this tutorial).
3. Running an Ubuntu Container
We’ll use an example Docker Compose configuration file called docker-compose.yml to define a service:
services:
demo:
image: ubuntu
3.1. Starting a Docker Compose Service
Let’s run the service’s container in the background by using the -d option:
$ docker-compose up -d
The output from the command indicates that it creates a container:
Creating ubuntu_demo_1 ... done
Let’s find out whether the container created by the Docker Compose service is running. We use the docker ps -a command to list the containers:
$ docker ps -a
The result is a list of containers, both running and exited:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a66cec15e72c ubuntu "/bin/bash" 10 seconds ago Exited (0) 9 seconds ago ubuntu_demo_1
The ubuntu_demo_1 container started by the Docker Compose service exits soon after being created.
With our setup now complete, let’s look at the following ways to keep this container running:
- Allocating a TTY
- Using the tail Command
- Using the sleep Command
- Using an infinite loop script
3.2. Cleaning Up Between Tests
While going through each of these examples, it’ll be helpful to remove containers between each one of the strategies that we demonstrate. We can do that with the docker-compose down command:
$ docker-compose down
Stopping ubuntu_demo_1 ... done
Removing ubuntu_demo_1 ... done
Removing network ubuntu_default
That also removes any networks or volumes created for a Docker Compose service.
4. Allocating a TTY
We can allocate a psuedo-TTY to the example Ubuntu container to keep it running. It’s called “psuedo” because no physical device drivers are used; a pseudo-terminal driver makes the container look like a terminal connection session.
Let’s allocate a TTY by adding the key: value dictionary pair tty: true to the example configuration file:
services:
demo:
image: ubuntu
tty: true
And now, we create the service’s container by using the same command:
$ docker-compose up -d
We’ll see that it’s still running:
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
dcc44f90a9eb ubuntu "/bin/bash" 16 seconds ago Up 13 seconds ubuntu_demo_1
The ubuntu_demo_1 container started by the Docker Compose service is running. When we attach a TTY, the Docker daemon keeps the main process running and this keeps the container running.
5. Using the tail Command
Another way that we have to keep our container running is the tail command:
services:
demo:
image: ubuntu
command: "tail -f /dev/null"
What we’re doing here is using the tail command along with its -f option to read continually from stdin on a TTY. By using /dev/null, tail “reads” this device in perpetuity, thus keeping our container running.
Let’s start our Docker Compose service and afterward list all running containers:
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5b293dbf6cc9 ubuntu "tail -f /dev/null" 18 seconds ago Up 17 seconds ubuntu_demo_1
Indeed, our ubuntu_demo_1 container is still running.
6. Using the sleep Command
We can keep our container running with the sleep command:
services:
demo:
image: ubuntu
command: "sleep infinity"
This works because it creates a TTY in our container’s bash shell. Using infinity means that the command never completes, keeping our container running forever.
Let’s again create the service’s container. Afterward, let’s list all running containers:
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c55a8b1f3839 ubuntu "sleep infinity" 2 minutes ago Up 2 minutes ubuntu_demo_1
Indeed, the container is running.
7. Using an Infinite Loop Script
Furthermore, we can run an infinite loop script within a container started with Docker Compose using a combination of while and do commands to keep the container running. Let’s see with an example:
services:
demo:
image: ubuntu
command: "sh -c 'while true; do sleep 1; done'"
Let’s run the modified Docker Compose service, and afterward, list all running containers:
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1f690849f774 ubuntu "sh -c 'while true; …" 14 seconds ago Up 12 seconds ubuntu_demo_1
The Docker container keeps on running because the while true condition is, well, always true.
An advantage of using an infinite while loop script over just the sleep infinity command is that a user can run other commands within the while loop to perform tasks, such as output log messages or find process status.
8. Why Doesn’t This Work for hello-world?
Interestingly, we can’t use these methods to keep all types of containers running. All our examples above assume that we can start a bash shell within a container to run a Linux command. This works well for a Docker Compose service created using the ubuntu image or any other Linux-based image.
As an example, though, we can’t keep a hello-world image-based container running. To find out why not, let’s review the Dockerfile for the hello-world image:
FROM scratch
COPY hello /
CMD ["/hello"]
The scratch image is typically used to build base images and doesn’t provide a Linux environment by itself. Without a Linux environment, there is no shell in which to run a command.
9. Conclusion
In this tutorial, we learned about the different methods to keep a Docker container running in Docker Compose. Let’s remember that we don’t need to perform an explicit configuration for all Docker containers to keep them running. This is because some Docker containers start an application, or a process, within a container as their typical functioning, which keeps the container running by default.
Further, we can’t keep all types of containers running because some containers don’t enable running a Bash shell and other Linux commands within them.
The code backing this article is available on GitHub. Once you're
logged in as a Baeldung Pro Member, start learning and coding on the project.