Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

docker-compose: difference run, exec and what happens to the layers

As far as I understood docker and docker-compose the flow is like that:

Dockerfile --build--> Docker Image --create--> Docker Container #1
                                   --create--> Docker Container #2
                                   --create--> ...
                                   --create--> Docker Container #n

So the Image is made by the commands in the Dockerfile but is purely some sort of "offline" version. You can bring it "online" then as a Container.

In my understanding docker-compose up is doing this 2-step process for us by the service definitions inside docker-compose.yml, beside some other stuff like mounting volumes, exposing ports and so on.


My first question is:

If I run docker-compose exec some_command, then I am manipulating the docker container - right?

As far as I have understood the documentation, docker-compose run some_command is doing nearly the same, but, it is creating a new "layer" beforehand, so sth. like this:

Docker Container #n (layer 0) ----> Docker Container #n (layer 1)

While the some_command is then executed on the layer 1 - right? Furthermore it seems like run is passing some_command to the entrypoint, while exec does override the defined entrypoint.

My second question is:

What happens to the new layer created by run afterwards? Is there a way to execute the command without creating a new layer? I would like to know that because I added an entrypoint-script like this:

#!/bin/sh

case "$@" in
    "update")
        pip3 install -r requirements.txt
        ;;
    "start")
        python manage.py runserver
        ;;
    *)
        echo "executing $@"
        exec "$@"
        ;;
esac

I did so in order to be able to run some common tasks like setup, update, ... by running just a short command like docker-compose run backend update. The problem is that the layer that is created by run is not used if I run up afterwards.

like image 792
ataraxis Avatar asked Dec 02 '20 00:12

ataraxis


People also ask

What is the difference between Docker Exec and run?

Docker Run vs Docker Exec! This is a fairly common question – but has a simple answer! In short, docker run is the command you use to create a new container from an image, whilst docker exec lets you run commands on an already running container!

What is the difference between docker run and docker compose?

The key difference between docker run versus docker-compose is that docker run is entirely command line based, while docker-compose reads configuration data from a YAML file. The second major difference is that docker run can only start one container at a time, while docker-compose will configure and run multiple.

What is docker compose exec command?

Description. This is the equivalent of docker exec targeting a Compose service. With this subcommand you can run arbitrary commands in your services. Commands are by default allocating a TTY, so you can use a command such as docker compose exec web sh to get an interactive prompt.

What happens when you run docker compose up?

The docker compose up command aggregates the output of each container (like docker compose logs --follow does). When the command exits, all containers are stopped. Running docker compose up --detach starts the containers in the background and leaves them running.


Video Answer


3 Answers

First lets clarify the basic terms here. Citing from the official docs:

An image is a read-only template with instructions for creating a Docker container.

Basically an image is just a filesystem archive containing the files and directory structure and some additional metadata (like ENV or CMD) for a container. So you may also think of it as a tar or zip archive.

A container is a runnable instance of an image.

To start a new container from an image docker unpacks the image, sets up the respective namespaces and starts a process using the unpacked image as the filesystem root.

The layers usually refer to the layers in the image created by the build process and are used to cache and reuse common files/layers in multiple images. So you should only need to care about them when optimizing your build steps in the Dockerfile.

Instead of extracting/copying the image contents for each new container docker will only add a thin writable layer on top of the image where changes for that container are stored. The exact mechanics of this are an implementation detail and depend on the used storage drivers! For the most part when using docker you should not need to care about this.


Now on using docker-compose: what it basically does under the hood is to just build and execute the respective docker commands* to run and manage the containers required for your services based on your docker-compose.yml config file. So lets look into the relations of docker-compose to the respective docker commands:

  • docker-compose build: will execute the respective docker build command for each service with a specified build option
  • docker-compose pull: docker pulls the images for all services with an image option from the respective repository
  • docker-compose up:
    • if necessary: will first docker-compose build / docker-compose pull all your services
    • starts the service containers by building and executing the respective docker run command
  • docker-compose exec: will run docker exec to start a new process/command inside an existing, running container
  • docker-compose run: starts a new container for interactively executing the specified command, i.e. executing docker run

* Note that docker-compose does not actually call the docker command but rather talks with the docker daemon directly using the API. But for easier understanding you can just pretend it does.


With that now to your questions:

If I run docker-compose exec some_command, then I am manipulating the docker container - right?

As far as I have understood the documentation, docker-compose run some_command is doing nearly the same, but, it is creating a new "layer" beforehand, so sth. like this:

docker-compose exec will run inside your existing, running service container while docker-compose run will start a new, independent container.

Furthermore it seems like run is passing some_command to the entrypoint, while exec does override the defined entrypoint.

Correct, exec does not use the entrypoint.

What happens to the new layer created by run afterwards? Is there a way to execute the command without creating a new layer?

The changes are part of the container started by run and will persist until you delete that container. If you want to apply the changes to your service container (used by docker-compose up) however you need to run docker-compose exec!

I would like to know that because I added an entrypoint-script like this:

I did so in order to be able to run some common tasks like setup, update, ... by running just a short command like docker-compose run backend update.

Since docker-compose run will execute that command in a new container you need to run docker-compose exec instead calling the entrypoint manually:

docker-compose exec app /entrypoint.sh update

# and if you need to restart your app restart the service containers
docker-compose restart

This however is the wrong way to use docker! because containers are meant to be ephemeral and commands executed in a container would only affect that particular container. The changes would all be lost the moment you had to run a new container instance on that image (you stopped the container, power failure, etc).

Instead you should add the update command as (the last) command into your Dockerfile and rebuild (the last layer of) the image:

docker-compose up --build

Or if you have to skip the build cache to force a new build:

docker-compose build --no-cache
docker-compose up
like image 122
acran Avatar answered Oct 19 '22 16:10

acran


For the most part, the various imperative docker-compose commands are very similar to their docker equivalents, except that they know how to look up containers using a docker-compose.yml file and in some cases their defaults are a little different.

So, docker-compose exec is basically identical to docker exec. It is a debugging tool that can run an additional command inside a running container. It runs in the same environment as the container's main process, with the same container-private filesystem. It is not a child process of the main process, and if the main process has an entrypoint wrapper script that sets environment variables, it won't get to see those. The additional command is frequently a debugging shell, and docker-compose exec defaults to the docker exec -it options.

# Get a debugging shell in a running container:
docker-compose exec app bash

# Basically equivalent to:
docker exec -it project_app_1 bash

Similarly, docker-compose run is basically identical to docker run: it launches a new container based on the image specified in the docker-compose.yml file. Unlike docker run, it inherits most of its options from the docker-compose.yml file, including network setup, environment variables, and mounted volumes (but not published ports). Like docker run, the contents of the container filesystem are basically orphaned after the container exits (or deleted if you used the --rm option).

# Get a debugging shell to see how an image was built:
# (This will run via the image's entrypoint)
docker-compose run --rm app bash

# Basically equivalent to:
docker build -t project_app .
docker run -it --net project_default -v $PWD/logs:/app/logs \
  --rm project_app bash
# (Picking up the Compose networks: and volumes:)

The "update" task you're showing in the proposed entrypoint wrapper isn't something you'd do at container startup time. If you need to update the code or library dependencies in the image, rebuild the image (maybe with docker-compose build).

# If the Dockerfile has...
FROM python:3.9
WORKDIR /app
COPY requirements.txt .
RUN pip3 install -r requirements.txt
...
# ...then you can get a container with updated library dependencies by
docker-compose up --build
like image 6
David Maze Avatar answered Oct 19 '22 15:10

David Maze


The 'live' layer 1 you're referring to is an actual folder squirreled away in the host filesystem, which is what the running container sees as its world of files. These files are ephemeral and are removed as soon as the container is removed, unless they are committed into a compressed image layer beforehand.

There are therefore two kinds of layer - the live ephemeral one and the persisted committed ones. Usually only the latter are called layers, though you're right they're all layer-like if you squint a bit.

An ephemeral layer can be turned into a persisted one only if it is committed (can be done via docker commit) , which is exactly what the docker build process does as it steps through the lines of the Dockerfile, running short-lived containers as it goes.


On question one: docker exec runs a new process in an existing squirreled-away folder as referred to above, whereas docker run, amongst other things, unpacks an archived image and sets up the 'online' files first. So basically you're completely right.

On question two: If you want to run your update command and then commit the container's ephemeral files that have been updated, you'll need to explicitly do docker commit $containerId $newImageTag after the docker run command has completed (but before the container has been removed - confused??)

Don't use docker-compose for this kind of container/image wrangling - use the docker command directly.

like image 2
Jason Holloway Avatar answered Oct 19 '22 15:10

Jason Holloway