diff --git a/.github/workflows/ci-oci-docker-install.yml b/.github/workflows/ci-oci-docker-install.yml new file mode 100644 index 0000000000..2f45f7be2e --- /dev/null +++ b/.github/workflows/ci-oci-docker-install.yml @@ -0,0 +1,129 @@ +name: Install OneClickInstall Docker + +on: + pull_request: + types: [opened, reopened, synchronize] + paths: + - '.github/workflows/ci-oci-docker-install.yml' + - 'install/OneClickInstall/install-Docker.sh' + workflow_dispatch: + inputs: + offline: + description: 'Publish offline self-extracting archive' + required: true + default: false + type: boolean + +jobs: + Install-OneClickInstall-Docker: + runs-on: ubuntu-22.04 + steps: + - name: Determine Branch Name + id: set-branch-name + run: | + BRANCH_NAME=$([ "${{ github.event_name }}" = "pull_request" ] && echo "${{ github.event.pull_request.head.ref }}" || echo "${GITHUB_REF#refs/heads/}") + echo "BRANCH_NAME=master" >> $GITHUB_ENV + + - name: Test OCI docker scripts + run: | + sudo docker image prune --all --force + + wget https://download.onlyoffice.com/docspace/docspace-enterprise-install.sh + sed '/bash install-Docker.sh/i sed -i "1i set -x" install-Docker.sh' -i docspace-enterprise-install.sh + sudo bash docspace-enterprise-install.sh docker -skiphc true -noni true $([ ${{ env.BRANCH_NAME }} != "master" ] && echo "-gb ${{ env.BRANCH_NAME }} -s 4testing-") || exit $? + + echo -n "Waiting for all containers to start..." + timeout 300 bash -c 'while docker ps | grep -q "starting"; do sleep 5; done' && echo "OK" || echo "container_status=timeout" >> $GITHUB_ENV + + - name: Check container status + run: | + docker ps --all --format "{{.Names}}" | xargs -I {} sh -c ' + status=$(docker inspect --format="{{if .State.Health}}{{.State.Health.Status}}{{else}}no healthcheck{{end}}" {}); + case "$status" in + healthy) color="\033[0;32m" ;; # green + "no healthcheck") color="\033[0;33m" ;; # yellow + *) color="\033[0;31m"; echo "container_status=red" >> $GITHUB_ENV ;; # red + esac; + printf "%-50s ${color}%s\033[0m\n" "{}:" "$status"; + ' + + - name: Print logs for crashed container + run: | + docker ps --all --format "{{.Names}}" | xargs -I {} sh -c ' + status=$(docker inspect --format="{{if .State.Health}}{{.State.Health.Status}}{{else}}no healthcheck{{end}}" {}); + case "$status" in + healthy | "no healthcheck") ;; + *) + echo "Logs for container {}:"; + docker logs --tail 30 {} | sed "s/^/\t/g"; + ;; + esac; + ' + case "${{ env.container_status }}" in + timeout) echo "Timeout reached. Not all containers are running."; exit 1 ;; + red) echo "One or more containers have status 'red'. Job will fail."; exit 1 ;; + esac + + - name: Checkout repository + if: ${{ github.event.inputs.offline == 'true' }} + uses: actions/checkout@v4 + with: + ref: feature/oci-offline + + - name: Creating offline self-extracting archive + if: ${{ github.event.inputs.offline == 'true' }} + run: | + INSTALL_PATH=${{ github.workspace }}/install + + docker stop $(docker ps -a -q) && docker rm $(docker ps -a -q) && docker volume rm $(docker volume ls -q) + sudo rm -rf /usr/local/lib/android /opt/ghc + + [ "${{ env.BRANCH_NAME }}" != "master" ] && STATUS="4testing-" && sed -i "s~\(STATUS=\"\).*\"$~\14testing-\"~" "${INSTALL_PATH}/OneClickInstall/install-Docker.sh" + sed -i 's~\(OFFLINE_INSTALLATION="\|SKIP_HARDWARE_CHECK="\).*"$~\1true"~' "${INSTALL_PATH}/OneClickInstall/install-Docker.sh" + + echo "Creating offline self-extracting archive..." + docker save onlyoffice/docspace-api | xz --verbose -T0 -z -9e > ${INSTALL_PATH}/docker_images.tar.xz + cd ${INSTALL_PATH}/docker && tar -czvf ${INSTALL_PATH}/docker.tar.gz --exclude='config/supervisor*' *.yml .env config/ + + tar -cvf ${INSTALL_PATH}/offline-docspace.tar \ + -C "${INSTALL_PATH}/OneClickInstall" install-Docker.sh \ + -C "${INSTALL_PATH}" docker_images.tar.xz \ + -C "${INSTALL_PATH}" docker.tar.gz + rm -rf ${INSTALL_PATH}/docker_images.tar.xz ${INSTALL_PATH}/docker.tar.gz + + echo "ARTIFACT_NAME=${ARTIFACT_NAME:=${STATUS}offline-docspace-installation.sh}" >> $GITHUB_ENV + [ "${{ env.BRANCH_NAME }}" = "master" ] && echo "ARTIFACT_VERSION_NAME=${STATUS}offline-docspace-$(docker images onlyoffice/docspace-api --format "{{.Tag}}")-installation.sh" >> $GITHUB_ENV + + cat ${INSTALL_PATH}/common/self-extracting.sh ${INSTALL_PATH}/offline-docspace.tar > ${INSTALL_PATH}/${ARTIFACT_NAME} + chmod +x ${INSTALL_PATH}/${ARTIFACT_NAME} + + + - name: Configure AWS Credentials + if: ${{ github.event.inputs.offline == 'true' }} + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID_OCI }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY_OCI }} + aws-region: us-east-1 + + - name: Upload offline self-extracting archive + if: ${{ github.event.inputs.offline == 'true' }} + run: | + aws s3 cp ${{ github.workspace }}/install/${{ env.ARTIFACT_NAME }} \ + ${{ secrets.AWS_BUCKET_URL_OCI }}/${{ env.ARTIFACT_NAME }} \ + --acl public-read \ + --content-type application/x-shellscript \ + --metadata-directive REPLACE + aws cloudfront create-invalidation \ + --distribution-id ${{ secrets.AWS_DISTRIBUTION_ID_OCI }} \ + --paths "/docspace/${{ env.ARTIFACT_NAME }}" + + - name: Upload offline self-extracting archive with version + if: ${{ env.ARTIFACT_VERSION_NAME != '' }} + run: | + aws s3 sync ${{ secrets.AWS_BUCKET_URL_OCI }}/${{ env.ARTIFACT_NAME }}\ + ${{ secrets.AWS_BUCKET_URL_OCI }}/${{ env.ARTIFACT_VERSION_NAME }} \ + --exact-timestamps + aws cloudfront create-invalidation \ + --distribution-id ${{ secrets.AWS_DISTRIBUTION_ID_OCI }} \ + --paths "/docspace/${{ env.ARTIFACT_VERSION_NAME }}" diff --git a/install/OneClickInstall/install-Docker.sh b/install/OneClickInstall/install-Docker.sh index 5272fba581..9d6da8eb83 100644 --- a/install/OneClickInstall/install-Docker.sh +++ b/install/OneClickInstall/install-Docker.sh @@ -39,7 +39,7 @@ PROXY_YML="${BASE_DIR}/proxy.yml" STATUS="" DOCKER_TAG="" INSTALLATION_TYPE="ENTERPRISE" -IMAGE_NAME="${PACKAGE_SYSNAME}/${PRODUCT}-api" +IMAGE_NAME="${PACKAGE_SYSNAME}/${STATUS}${PRODUCT}-api" CONTAINER_NAME="${PACKAGE_SYSNAME}-api" NETWORK_NAME=${PACKAGE_SYSNAME} @@ -105,8 +105,9 @@ LETS_ENCRYPT_DOMAIN="" LETS_ENCRYPT_MAIL="" HELP_TARGET="install-Docker.sh"; +OFFLINE_INSTALLATION="false" -SKIP_HARDWARE_CHECK="false"; +SKIP_HARDWARE_CHECK="false" EXTERNAL_PORT="80" @@ -491,6 +492,13 @@ while [ "$1" != "" ]; do shift fi ;; + + -off | --offline ) + if [ "$2" != "" ]; then + OFFLINE_INSTALLATION=$2 + shift + fi + ;; -? | -h | --help ) echo " Usage: bash $HELP_TARGET [PARAMETER] [[PARAMETER], ...]" @@ -543,6 +551,7 @@ while [ "$1" != "" ]; do echo " -lem, --letsencryptmail defines the domain administator mail address for Let's Encrypt certificate" echo " -cf, --certfile path to the certificate file for the domain" echo " -ckf, --certkeyfile path to the private key file for the certificate" + echo " -off, --offline set the script for offline installation (true|false)" echo " -noni, --noninteractive auto confirm all questions (true|false)" echo " -dbm, --databasemigration database migration (true|false)" echo " -ms, --makeswap make swap file (true|false)" @@ -1046,7 +1055,7 @@ retrieving_tag_from_hub () { if ! command_exists jq ; then if command_exists yum; then if ! rpm -q epel-release > /dev/null 2>&1; then - rpm -ivh https://dl.fedoraproject.org/pub/epel/epel-release-latest-$REV.noarch.rpm + [ "${OFFLINE_INSTALLATION}" = "false" ] && rpm -ivh https://dl.fedoraproject.org/pub/epel/epel-release-latest-$REV.noarch.rpm fi fi install_service jq @@ -1079,7 +1088,7 @@ retrieving_tag_from_hub () { } get_available_version () { - retrieving_tag_from_hub ${1} + [ "${OFFLINE_INSTALLATION}" = "false" ] && retrieving_tag_from_hub ${1} || TAGS_RESP=$(docker images --format "{{.Tag}}" ${1}) VERSION_REGEX='^[0-9]+\.[0-9]+(\.[0-9]+){0,2}$' [ ${#TAGS_RESP[@]} -eq 1 ] && LATEST_TAG="${TAGS_RESP[0]}" || LATEST_TAG=$(printf "%s\n" "${TAGS_RESP[@]}" | grep -E "$VERSION_REGEX" | sort -V | tail -n 1) @@ -1087,7 +1096,11 @@ get_available_version () { if [ ! -z "${LATEST_TAG}" ]; then echo "${LATEST_TAG}" | sed "s/\"//g" else - echo "Unable to retrieve tag from ${1} repository" >&2 + if [ "${OFFLINE_INSTALLATION}" = "false" ]; then + echo "Unable to retrieve tag from ${1} repository" >&2 + else + echo "Error: The image '${1}' is not found in the local Docker registry." >&2 + fi kill -s TERM $PID fi } @@ -1173,7 +1186,11 @@ set_installation_type_data () { download_files () { if ! command_exists docker-compose; then - install_docker_compose + [ "${OFFLINE_INSTALLATION}" = "false" ] && install_docker_compose || { echo "docker-compose not installed"; exit 1; } + else + if [ "$(docker-compose --version | grep -oP '(?<=v)\d+\.\d+'| sed 's/\.//')" -lt "21" ]; then + [ "$OFFLINE_INSTALLATION" = "false" ] && install_docker_compose || { echo "docker-compose version is outdated"; exit 1; } + fi fi # Fixes issues with variables when upgrading to v1.1.3 @@ -1181,7 +1198,7 @@ download_files () { for HOST in "${HOSTS[@]}"; do [[ "${!HOST}" == *CONTAINER_PREFIX* || "${!HOST}" == *$PACKAGE_SYSNAME* ]] && export "$HOST="; done [[ "${APP_URL_PORTAL}" == *${PACKAGE_SYSNAME}-proxy* ]] && APP_URL_PORTAL="" - echo -n "Downloading configuration files to the ${BASE_DIR} directory..." + [ "${OFFLINE_INSTALLATION}" = "false" ] && echo -n "Downloading configuration files to ${BASE_DIR}..." || echo "Unzip docker.tar.gz to ${BASE_DIR}..." if ! command_exists tar; then install_service tar @@ -1190,15 +1207,25 @@ download_files () { [ -d "${BASE_DIR}" ] && rm -rf "${BASE_DIR}" mkdir -p ${BASE_DIR} - if [ -z "${GIT_BRANCH}" ]; then - curl -sL -o docker.tar.gz "https://download.${PACKAGE_SYSNAME}.com/${PRODUCT}/docker.tar.gz" - tar -xf docker.tar.gz -C ${BASE_DIR} + + if [ "${OFFLINE_INSTALLATION}" = "false" ]; then + if [ -z "${GIT_BRANCH}" ]; then + DOWNLOAD_URL="https://download.${PACKAGE_SYSNAME}.com/${PRODUCT}/docker.tar.gz" + else + DOWNLOAD_URL="https://github.com/${PACKAGE_SYSNAME}/${PRODUCT}-buildtools/archive/${GIT_BRANCH}.tar.gz" + STRIP_COMPONENTS="--strip-components=3 --wildcards */install/docker/*" + fi + + curl -sL "${DOWNLOAD_URL}" | tar -xzf - -C "${BASE_DIR}" ${STRIP_COMPONENTS} else - curl -sL -o docker.tar.gz "https://github.com/${PACKAGE_SYSNAME}/${PRODUCT}-buildtools/archive/${GIT_BRANCH}.tar.gz" - tar -xf docker.tar.gz --strip-components=3 -C ${BASE_DIR} --wildcards '*/install/docker/*' + if [ -f "$(dirname "$0")/docker.tar.gz" ]; then + tar -xf $(dirname "$0")/docker.tar.gz -C "${BASE_DIR}" + else + echo "Error: docker.tar.gz not found in the same directory as the script." + echo "You need to download the docker.tar.gz file from https://download.${PACKAGE_SYSNAME}.com/${PRODUCT}/docker.tar.gz" + exit 1 + fi fi - - rm -rf docker.tar.gz echo "OK" @@ -1226,6 +1253,7 @@ install_mysql_server () { reconfigure MYSQL_VERSION ${MYSQL_VERSION} if [[ -z ${MYSQL_HOST} ]] && [ "$INSTALL_MYSQL_SERVER" == "true" ]; then + offline_check_docker_image ${BASE_DIR}/db.yml docker-compose -f $BASE_DIR/db.yml up -d elif [ "$INSTALL_MYSQL_SERVER" == "pull" ]; then docker-compose -f $BASE_DIR/db.yml pull @@ -1254,6 +1282,7 @@ install_document_server () { install_rabbitmq () { if [[ -z ${RABBIT_HOST} ]] && [ "$INSTALL_RABBITMQ" == "true" ]; then + offline_check_docker_image ${BASE_DIR}/rabbitmq.yml docker-compose -f $BASE_DIR/rabbitmq.yml up -d elif [ "$INSTALL_RABBITMQ" == "pull" ]; then docker-compose -f $BASE_DIR/rabbitmq.yml pull @@ -1269,6 +1298,7 @@ install_rabbitmq () { install_redis () { if [[ -z ${REDIS_HOST} ]] && [ "$INSTALL_REDIS" == "true" ]; then + offline_check_docker_image ${BASE_DIR}/redis.yml docker-compose -f $BASE_DIR/redis.yml up -d elif [ "$INSTALL_REDIS" == "pull" ]; then docker-compose -f $BASE_DIR/redis.yml pull @@ -1289,6 +1319,7 @@ install_elasticsearch () { else sed -i 's/Xms[0-9]g/Xms1g/g; s/Xmx[0-9]g/Xmx1g/g' $BASE_DIR/opensearch.yml fi + offline_check_docker_image ${BASE_DIR}/opensearch.yml docker-compose -f $BASE_DIR/opensearch.yml up -d elif [ "$INSTALL_ELASTICSEARCH" == "pull" ]; then docker-compose -f $BASE_DIR/opensearch.yml pull @@ -1325,6 +1356,9 @@ install_fluent_bit () { reconfigure DASHBOARDS_USERNAME "${DASHBOARDS_USERNAME:-"${PACKAGE_SYSNAME}"}" reconfigure DASHBOARDS_PASSWORD "${DASHBOARDS_PASSWORD:-$(get_random_str 20)}" + offline_check_docker_image ${BASE_DIR}/fluent.yml + offline_check_docker_image ${BASE_DIR}/dashboards.yml + docker-compose -f ${BASE_DIR}/fluent.yml -f ${BASE_DIR}/dashboards.yml up -d elif [ "$INSTALL_FLUENT_BIT" == "pull" ]; then docker-compose -f ${BASE_DIR}/fluent.yml -f ${BASE_DIR}/dashboards.yml pull @@ -1349,13 +1383,23 @@ install_product () { reconfigure APP_URL_PORTAL "${APP_URL_PORTAL:-"http://${PACKAGE_SYSNAME}-router:8092"}" reconfigure EXTERNAL_PORT ${EXTERNAL_PORT} - if [[ -z ${MYSQL_HOST} ]] && [ "$INSTALL_MYSQL_SERVER" == "true" ]; then + if [[ -z ${MYSQL_HOST} ]] && [ "$INSTALL_MYSQL_SERVER" == "true" ] && [[ -n $(docker ps -q --filter "name=${PACKAGE_SYSNAME}-mysql-server") ]]; then echo -n "Waiting for MySQL container to become healthy..." (timeout 30 bash -c "while ! docker inspect --format '{{json .State.Health.Status }}' ${PACKAGE_SYSNAME}-mysql-server | grep -q 'healthy'; do sleep 1; done") && echo "OK" || (echo "FAILED") fi + offline_check_docker_image ${BASE_DIR}/migration-runner.yml + offline_check_docker_image ${BASE_DIR}/${PRODUCT}.yml + offline_check_docker_image ${PROXY_YML} + offline_check_docker_image ${BASE_DIR}/notify.yml + offline_check_docker_image ${BASE_DIR}/healthchecks.yml + docker-compose -f $BASE_DIR/migration-runner.yml up -d - echo -n "Waiting for database migration to complete..." && docker wait ${PACKAGE_SYSNAME}-migration-runner && echo "OK" + if [[ -n $(docker ps -q --filter "name=${PACKAGE_SYSNAME}-migration-runner") ]]; then + echo -n "Waiting for database migration to complete..." + timeout 30 bash -c "while [ $(docker wait ${PACKAGE_SYSNAME}-migration-runner) -ne 0 ]; do sleep 1; done;" && echo "OK" || echo "FAILED" + fi + docker-compose -f $BASE_DIR/${PRODUCT}.yml up -d docker-compose -f ${PROXY_YML} up -d docker-compose -f $BASE_DIR/notify.yml up -d @@ -1405,6 +1449,16 @@ make_swap () { fi } +offline_check_docker_image() { + if [ "${OFFLINE_INSTALLATION}" != "false" ]; then + [ ! -f "$1" ] && { echo "Error: File '$1' does not exist."; exit 1; } + + docker-compose -f "$1" config | grep -oP 'image:\s*\K\S+' | while IFS= read -r IMAGE_TAG; do + docker images "${IMAGE_TAG}" | grep -q "${IMAGE_TAG%%:*}" || { echo "Error: The image '${IMAGE_TAG}' is not found in the local Docker registry."; kill -s TERM $PID; } + done + fi +} + check_hub_connection() { retrieving_tag_from_hub ${IMAGE_NAME} [ -z "$TAGS_RESP" ] && { echo -e "Unable to download tags from ${HUB:-hub.docker.com}.\nTry specifying another dockerhub name using -hub"; exit 1; } || true @@ -1435,16 +1489,16 @@ start_installation () { check_docker_version service docker start else - install_docker + [ "${OFFLINE_INSTALLATION}" = "false" ] && install_docker || { echo "docker not installed"; exit 1; } fi docker_login - check_hub_connection + [ "${OFFLINE_INSTALLATION}" = "false" ] && check_hub_connection create_network - domain_check + [ "${OFFLINE_INSTALLATION}" = "false" ] && domain_check if [ "$UPDATE" = "true" ]; then set_docspace_params diff --git a/install/common/self-extracting.sh b/install/common/self-extracting.sh new file mode 100644 index 0000000000..e9ec4e7558 --- /dev/null +++ b/install/common/self-extracting.sh @@ -0,0 +1,29 @@ +#!/bin/bash +set -e + +[ "$(id -u)" -ne 0 ] && { echo "To perform this action you must be logged in with root rights"; exit 1; } + +TEMP_DIR=$(mktemp -d) + +trap 'echo "Cleaning up temporary files..."; rm -rf "${TEMP_DIR}"' EXIT + +! type docker &> /dev/null && { echo "docker not installed"; exit 1; } +! type docker-compose &> /dev/null && { echo "docker-compose not installed"; exit 1; } + +echo "Extracting docker images to ${TEMP_DIR}..." +tail -n +$(awk '/^__END_OF_SHELL_SCRIPT__$/{print NR + 1; exit 0;}' "$0") "$0" | tar x -C "${TEMP_DIR}" + +echo "Loading docker images..." +docker load -i ${TEMP_DIR}/docker_images.tar.xz + +echo "Extracting OneClickInstall files to the current directory..." +mv -f ${TEMP_DIR}/docker.tar.gz $(dirname "$0")/docker.tar.gz +mv -f ${TEMP_DIR}/install-Docker.sh $(dirname "$0")/install-Docker.sh + +echo "Running the install-Docker.sh script..." +chmod +x $(dirname "$0")/install-Docker.sh +$(dirname "$0")/install-Docker.sh + +exit 0 + +__END_OF_SHELL_SCRIPT__ diff --git a/install/docker/dashboards.yml b/install/docker/dashboards.yml index 5c2b1e3bd7..d8d8dcb93d 100644 --- a/install/docker/dashboards.yml +++ b/install/docker/dashboards.yml @@ -9,6 +9,12 @@ services: - "SERVER_BASEPATH=/dashboards" expose: - "5601" + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:5601/api/status"] + interval: 10s + retries: 3 + start_period: 10s + timeout: 10s networks: default: diff --git a/install/docker/db.yml b/install/docker/db.yml index fee54b5502..76e2f676ea 100644 --- a/install/docker/db.yml +++ b/install/docker/db.yml @@ -17,9 +17,10 @@ services: MYSQL_USER: ${MYSQL_USER} MYSQL_PASSWORD: ${MYSQL_PASSWORD} healthcheck: - test: ["CMD-SHELL", "mysqladmin ping --silent"] + test: ["CMD", "mysql", "-u", "${MYSQL_USER}", "--password=${MYSQL_PASSWORD}", "-e", ";"] interval: 10s timeout: 5s + start_period: 10s retries: 3 volumes: - mysql_data:/var/lib/mysql diff --git a/install/docker/ds.yml b/install/docker/ds.yml index 715cab3802..60ba2c0f48 100644 --- a/install/docker/ds.yml +++ b/install/docker/ds.yml @@ -16,6 +16,12 @@ services: stdin_open: true restart: always stop_grace_period: 60s + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8000/info/info.json"] + interval: 30s + retries: 5 + start_period: 60s + timeout: 10s networks: default: diff --git a/install/docker/migration-runner.yml b/install/docker/migration-runner.yml index 51e34083ed..1ffc0f7f26 100644 --- a/install/docker/migration-runner.yml +++ b/install/docker/migration-runner.yml @@ -2,7 +2,7 @@ services: onlyoffice-migration-runner: image: "${HUB}${REPO}/${DOCKER_IMAGE_PREFIX}-migration-runner:${DOCKER_TAG}" container_name: ${MIGRATION_RUNNER_HOST} - restart: "no" + restart: "on-failure" environment: MYSQL_CONTAINER_NAME: ${MYSQL_CONTAINER_NAME} MYSQL_HOST: ${MYSQL_HOST} diff --git a/install/docker/opensearch.yml b/install/docker/opensearch.yml index 187423b400..df2c49e371 100644 --- a/install/docker/opensearch.yml +++ b/install/docker/opensearch.yml @@ -25,6 +25,12 @@ services: - "9600" # required for Performance Analyzer ports: - 127.0.0.1:9200:9200 + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9200/_cluster/health?pretty"] + interval: 30s + retries: 3 + start_period: 10s + timeout: 10s networks: default: diff --git a/install/docker/rabbitmq.yml b/install/docker/rabbitmq.yml index c9681697d7..7c5e55a42f 100644 --- a/install/docker/rabbitmq.yml +++ b/install/docker/rabbitmq.yml @@ -6,6 +6,13 @@ services: expose: - "5672" - "80" + healthcheck: + test: ["CMD", "rabbitmq-diagnostics", "status"] + interval: 10s + retries: 3 + start_period: 10s + timeout: 10s + networks: default: name: ${NETWORK_NAME} diff --git a/install/docker/redis.yml b/install/docker/redis.yml index 08c86dbc19..844bcf986e 100644 --- a/install/docker/redis.yml +++ b/install/docker/redis.yml @@ -5,6 +5,13 @@ services: restart: always expose: - "6379" + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + retries: 3 + start_period: 10s + timeout: 10s + networks: default: name: ${NETWORK_NAME}