Docker Compose中如何等待数据库就绪后再构建容器
执行docker-compose up -d my_service_dev时,需要等待mariadb_test容器完全就绪后,再开始构建my_service_dev镜像。现有docker-compose配置如下:
version: '3.8' services: mariadb_test: image: mariadb/server:10.5 ports: - 3306:3306 environment: - MYSQL_ROOT_PASSWORD=password - MYSQL_USER=root - MYSQL_ROOT_HOST=10.5.0.1 healthcheck: test: [ "CMD", "mysqladmin" ,"ping", "-h", "localhost" ] interval: 1m30s timeout: 10s retries: 3 networks: my_network: ipv4_address: 10.5.0.5 my_service_dev: build: context: . dockerfile: Dockerfile_my_service depends_on: mariadb_test: condition: service_completed_successfully environment: - DB_HOST=10.5.0.5 - DB_USER=root - DB_PASSWORD=password tty: true networks: my_network: ipv4_address: 10.5.0.6 networks: my_network: driver: bridge ipam: config: - subnet: 10.5.0.0/16 gateway: 10.5.0.1
对应的Dockerfile:
FROM debian:11.6 ENV DEBIAN_FRONTEND=noninteractive ARG DB_HOST ARG DB_USER ARG DB_PASSWORD ENV DB_HOST=${DB_HOST} ENV DB_USER=${DB_USER} ENV DB_PASSWORD=${DB_PASSWORD} RUN apt-get update && apt-get install -y --no-install-recommends apt-utils RUN apt-get install -y \ libmariadb3 \ wget WORKDIR /usr/local/bin RUN wget https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh RUN chmod +x wait-for-it.sh RUN ./wait-for-it.sh 10.5.0.5:3306 --timeout=40 -- echo "MariaDB is up" RUN mariadb --host=${DB_HOST} --port=3306 --user=${DB_USER} --password=${DB_PASSWORD} -e "CREATE USER 'test_user'@'' IDENTIFIED BY '12345';"
尝试过以下三种方案均无效:
尝试过的无效方案
方案1
在my_service_dev中配置:
depends_on: mariadb_test: condition: service_completed_successfully
问题:service_completed_successfully要求目标容器运行后退出,但MariaDB是长期运行服务,永远不会满足该条件,导致my_service_dev无法启动。
方案2
添加wait-for-db组件:
wait-for-db: image: atkrad/wait4x depends_on: - mariadb_test command: tcp mariadb_test:3306 -t 30s -i 250ms
同时修改my_service_dev的depends_on:
depends_on: wait-for-db: condition: service_completed_successfully
问题:Docker Compose在启动容器前会优先构建所有需要构建的镜像,wait-for-db容器还没启动,my_service_dev的镜像已经开始构建,无法达到等待效果。
方案3
在Dockerfile中执行wait-for-it.sh:
RUN ./wait-for-it.sh 10.5.0.5:3306 --timeout=40 -- echo "MariaDB is up"
问题:镜像构建阶段无法访问容器网络,此时mariadb_test容器尚未启动,导致等待超时。
可行解决方案
核心思路
Docker Compose的depends_on只能控制容器启动顺序,无法控制镜像构建顺序。镜像构建是独立于容器网络的离线过程,不应依赖外部运行中的服务。正确的做法是将数据库初始化逻辑移到容器启动阶段,同时分步骤控制容器启动与镜像构建的顺序。
步骤1:调整Dockerfile,将初始化逻辑移到容器启动阶段
修改后的Dockerfile:
FROM debian:11.6 ENV DEBIAN_FRONTEND=noninteractive ARG DB_HOST ARG DB_USER ARG DB_PASSWORD ENV DB_HOST=${DB_HOST} ENV DB_USER=${DB_USER} ENV DB_PASSWORD=${DB_PASSWORD} RUN apt-get update && apt-get install -y --no-install-recommends apt-utils RUN apt-get install -y \ libmariadb3 \ wget \ mariadb-client # 安装mariadb客户端工具 WORKDIR /usr/local/bin RUN wget https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh RUN chmod +x wait-for-it.sh # 复制初始化脚本到容器 COPY init-db.sh /usr/local/bin/ RUN chmod +x init-db.sh # 容器启动时执行初始化脚本 ENTRYPOINT ["/usr/local/bin/init-db.sh"]
创建init-db.sh脚本(与Dockerfile同目录):
#!/bin/bash # 等待MariaDB服务就绪 ./wait-for-it.sh ${DB_HOST}:3306 --timeout=40 -- echo "MariaDB is up" # 执行数据库初始化(添加IF NOT EXISTS避免重复执行报错) mariadb --host=${DB_HOST} --port=3306 --user=${DB_USER} --password=${DB_PASSWORD} -e "CREATE USER IF NOT EXISTS 'test_user'@'%' IDENTIFIED BY '12345';" # 保持容器运行(根据实际业务调整,若有其他进程可替换) exec tail -f /dev/null
步骤2:调整docker-compose.yml,依赖健康状态
修改mariadb_test的健康检查配置(缩短间隔,更快检测就绪),并调整my_service_dev的depends_on为依赖健康状态:
version: '3.8' services: mariadb_test: image: mariadb/server:10.5 ports: - 3306:3306 environment: - MYSQL_ROOT_PASSWORD=password - MYSQL_ROOT_HOST=10.5.0.1 healthcheck: test: [ "CMD", "mysqladmin" ,"ping", "-h", "localhost", "-u", "root", "-ppassword" ] interval: 10s timeout: 5s retries: 5 networks: my_network: ipv4_address: 10.5.0.5 my_service_dev: build: context: . dockerfile: Dockerfile_my_service args: - DB_HOST=10.5.0.5 - DB_USER=root - DB_PASSWORD=password depends_on: mariadb_test: condition: service_healthy # 等待MariaDB容器进入健康状态 environment: - DB_HOST=10.5.0.5 - DB_USER=root - DB_PASSWORD=password tty: true networks: my_network: ipv4_address: 10.5.0.6 networks: my_network: driver: bridge ipam: config: - subnet: 10.5.0.0/16 gateway: 10.5.0.1
步骤3:分批次执行命令(可选,严格先启动数据库再构建)
如果必须先启动mariadb_test再构建my_service_dev镜像,可通过以下脚本自动化:
#!/bin/bash # 启动MariaDB容器 docker-compose up -d mariadb_test # 等待MariaDB进入健康状态(需Docker Compose 1.29+) docker-compose wait mariadb_test # 构建并启动my_service_dev docker-compose up -d my_service_dev
执行该脚本即可实现先启动数据库并等待就绪,再构建镜像并启动容器的效果。
内容的提问来源于stack exchange,提问作者Mircea

