Shadow
Shadow

Reputation: 2478

How to run multiple services sequentially with docker

I have 3 types of components in my docker app:

  1. database container
  2. database migrations container (creates and updates schema, used on production)
  3. database seeding (used on dev only)

When trying to run all of these services in docker compose I have a problem of ordering. I need database to be available, then migrations applied, then seed - in that exact order.

Simple docker compose up brings all services at once, so they fail because they cannot connect to database. On the other hand using docker compose up instead of calling docker run automatically brings services down.

How I can start my services, so they:

  1. Are run with docker
  2. Are started in correct order, and are waiting for each other
  3. Can easily be composed down with simple ctrl+c?

Upvotes: 5

Views: 6282

Answers (4)

rokpoto.com
rokpoto.com

Reputation: 10784

  • To achieve ordering on services start using docker-compose use depends_on.
...
services:
   db:
     ...
   migrations:
      depends_on:
        - db
   ...
   seeding:
      depends_on:
        - db
   
  • However, starting in order may not be enough as db service may not be available for some time after start.

To solve that:

  • define healthcheck for db service.
  • use depends_on in the following manner in dependent on db services:
depends_on:
   db:  
     condition: service_healthy

See working demo example here.

  • for seeding service to run after migrations has completed you have 2 options:
  1. Use service_completed_successfully with depends_on in seeding service:
depends_on:
   migrations:  
     condition: service_completed_successfully

Thanks to @David Maze for suggestion.

  1. implement some busy wait script in seeding service command. More information is here. Busy wait script has to check for some condition signifying migrations were complete.
  • for stopping services stack use: docker compose down

Upvotes: 6

clarj
clarj

Reputation: 1043

I find the way Big Data Europe have packed their Apache images useful, using a service precondition script. For example, see this repo, although it would mean modifying/extending the docker images.

You could create an ENTRYPOINT file that contains the following:

function wait_for_it()
{
    local serviceport=$1
    local service=${serviceport%%:*}
    local port=${serviceport#*:}
    local retry_seconds=5
    local max_try=100
    let i=1

    nc -z $service $port
    result=$?

    until [ $result -eq 0 ]; do
      echo "[$i/$max_try] check for ${service}:${port}..."
      echo "[$i/$max_try] ${service}:${port} is not available yet"
      if (( $i == $max_try )); then
        echo "[$i/$max_try] ${service}:${port} is still not available; giving up after ${max_try} tries. :/"
        exit 1
      fi
      
      echo "[$i/$max_try] try in ${retry_seconds}s once again ..."
      let "i++"
      sleep $retry_seconds

      nc -z $service $port
      result=$?
    done
    echo "[$i/$max_try] $service:${port} is available."
}

for i in ${SERVICE_PRECONDITION[@]}
do
    wait_for_it ${i}
done

Then in the Dockerfile at the end:

COPY entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
CMD ---NORMAL STARTUP SCRIPT---

Then in your docker-compose.yml, add environmental variables to the containers that need to wait on another server going live. For example:

db:
    ...
migrations:
    ...
    environment:
      SERVICE_PRECONDITION: "db:5432 ... ..."

Once the required server is responding on that host name and port (i.e. the database is running), the container will continue to the normal command.

Upvotes: 0

atline
atline

Reputation: 31654

You could use depends on, e.g.

version: "3.9"
services:
  web:
    build: .
    depends_on:
      - db
      - redis
  redis:
    image: redis
  db:
    image: postgres

But:

depends_on does not wait for db and redis to be “ready” before starting web - only until they have been started. If you need to wait for a service to be ready, see Controlling startup order for more on this problem and strategies for solving it.

For controlling startup order, you need explicitly write script to wait other container starts to work, e.g. if you want to make sure a db container really starts, you could let the app container connect to the db container to have try. If link successful, means the db container starts, otherwise, loop to try again.

E.g. example extract from above documention:

  • Use a tool such as wait-for-it, dockerize, sh-compatible wait-for, or RelayAndContainers template. These are small wrapper scripts which you can include in your application’s image to poll a given host and port until it’s accepting TCP connections.

For example, to use wait-for-it.sh or wait-for to wrap your service’s command:

version: "2" 
  services:   web:
    build: .
    ports:
      - "80:8000"
    depends_on:
      - "db"
    command: ["./wait-for-it.sh", "db:5432", "--", "python", "app.py"]   db:
    image: postgres
  • Alternatively, write your own wrapper script to perform a more application-specific health check. For example, you might want to wait until Postgres is ready to accept commands:
#!/bin/sh
# wait-for-postgres.sh

set -e
  
host="$1"
shift
 
until PGPASSWORD=$POSTGRES_PASSWORD psql -h "$host" -U "postgres" -c '\q'; do
  >&2 echo "Postgres is unavailable - sleeping"
 sleep 1
done
 
>&2 echo "Postgres is up - executing command"
exec "$@" 

You can use this as a wrapper script as in the previous example, by setting:

command: ["./wait-for-postgres.sh", "db", "python", "app.py"]

Finally,

Can easily be composed down with simple ctrl+c?

I think docker-compose down is not so complex, isn't it?

Upvotes: 1

Antonio Petricca
Antonio Petricca

Reputation: 11040

You should use the depends_on keywork as per the following example:

version: '3.5'

services:
    db:
        image: mysql:latest
        container_name: db
        ports:
          - "3306:3306"
        volumes:
          - ${SOURCES_PATH}/../volumes/mysql:/var/lib/mysql
        restart: always
        environment:
            MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
            MYSQL_DATABASE: wordpress
            MYSQL_USER: ${DB_USER}
            MYSQL_PASSWORD: ${DB_PASSWORD}

    phpmyadmin:
        depends_on:
          - db
        image: phpmyadmin/phpmyadmin:latest
        container_name: phpmyadmin
        ports:
          - "8001:80"
        restart: always
        environment:
          - PMA_HOST=db
          - PMA_USER=${DB_USER}
          - PMA_PASSWORD=${DB_PASSWORD}
          - PMA_PORT=3306
        volumes:
          - phpmyadmin:/sessions

The dependency relation forces docker compose to start services in the correct order.

Upvotes: 0

Related Questions