-1

I am building a docker container for a couple of services and it occured to me that I don't really understand how to properly decide where to place certain commands.

It seems to me there are 2 alternatives when it comes to running commands on a Docker container.

  • Using the RUN instruction (e.g RUN composer -n install)
  • Running the same command above inside a script that is used as entrypoint for the container (i.e inside docker-entrypoint.sh)

With regards to best practices, how should one decide when to use one approach vs the other? Specially with regards to containers that are not meant to be the base for any others, should commands be entirely contained in the Dockerfile, with only the application startup be done with the entrypoint, or should some commands be moved to the entrypoint script?

Thanks.

markhc
  • 659
  • 6
  • 15
  • 1
    `RUN` is completely different from `CMD` or `ENTRYPOINT`. It executes at build time while the latter two execute at runtime. Does this answer your question? [What is the difference between CMD and ENTRYPOINT in a Dockerfile?](https://stackoverflow.com/questions/21553353/what-is-the-difference-between-cmd-and-entrypoint-in-a-dockerfile) – Selcuk May 10 '23 at 02:09
  • RUN only executes at Dockerfile build time. An entrypoint is the startup command that runs when the container starts up/is running. docker-entrypoint.sh is a convention used by Docker Compose and similar tools: https://stackoverflow.com/a/58439355/2125723 – ryanwebjackson May 10 '23 at 02:17
  • I understand the differences. What I am asking is about the tradeoffs of each alternative. Running commands at build time will incerase the image size, while running them at startup will obviously increase the startup time. Is there anything else to consider when choosing one vs the other? I am not new to Docker, this is more of a best-practices question I guess. – markhc May 10 '23 at 02:19
  • 1
    Run as much as possible at build time. Then the runtime is essentially "snapshotted" for reliability purposes. Image size is usually a pretty low priority consideration, especially for interpreted languages since their runtimes are so big anyway, and a multi stage build can probably help a good deal to get size within acceptable ranges. – erik258 May 10 '23 at 02:32
  • Did any of the two answers work for you? – Paolo May 16 '23 at 08:53

2 Answers2

0

Running commands at build time will increase the image size, while running them at startup will obviously increase the startup time. Is there anything else to consider when choosing one vs the other?

Everything that has to do with the building and configuration of software should take place in the build stage. The entrypoint should only perform the minimum set of tasks required to get the container up and running properly.

If you are concerned about image size, then you should most certainly adopt the best practice of combining statements in a single RUN command, as this will result in a single layer (so less overall size), e.g.:

RUN echo hello && \
    echo world

You may also look into multi-stage builds, which can help in reducing the final size of the image.

For reference: https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#minimize-the-number-of-layers

Paolo
  • 21,270
  • 6
  • 38
  • 69
0

You can run multiple copies of a container off of a single image. A Dockerfile RUN command only runs once when the image is built; the entrypoint script runs every time you start the container.

As a general rule, I might suggest doing as much as you can in the Dockerfile. There are some parts of the system that aren't available until runtime, like environment-variable settings and other containers; you can't access these until the container starts, which means the entrypoint script.

So, for example:

  • You should RUN composer ... and other commands that install software or libraries in the Dockerfile; these don't depend on the runtime environment and you don't want to repeat them every time the container runs.
  • If you have database migrations, you should COPY the migration files into the image, but the database isn't available at image-build time, so the actual migrations need to be run in the entrypoint script.
  • If you need to interact with the contents of volumes, those aren't available until the container starts, and this needs to be done in the entrypoint script. (But it's better to avoid needing this; prefer a database in another container to using mounted files for storage.)

I'm a little less familiar with PHP, but a typical Python example might look like:

# Dockerfile
FROM python:3.11
WORKDIR /app

# Install this application's library dependencies
# (Only once, during the image build)
COPY requirements.txt ./
RUN pip install -r requirements.txt

# Copy in the rest of the application code
# (Including the entrypoint script and database migrations)
COPY ./ ./

# Metadata to run the application
EXPOSE 8000
ENTRYPOINT ["./entrypoint.sh"]
CMD ["./manage.py", "runserver", "0.0.0.0:8000"]
#!/bin/sh
# entrypoint.sh

# Run migrations at application startup.  The database isn't
# available until now, and its contents are independent of
# this image.  Gets repeated every time the container starts.
./manage.py migrate

# Switch to the main container `CMD`.
exec "$@"
# docker-compose.yml
version: '3.8'
services:
  app:
    build: ./
    ports: ['8000:8000']
    environment:
      PGHOST: db
      other PostgreSQL-related variables: as well
    # Note, no volumes:
  db:
    image: postgres:14
    environment:
      POSTGRES_PASSWORD: passw0rd
      et: cetera
    volumes:
      - dbdata:/var/lib/postgresql/data
volumes:
  dbdata:
David Maze
  • 130,717
  • 29
  • 175
  • 215