Docker

    Add a restart policy to a container that was already created

    docker update --restart=always <container>
    

    source

    Change data directory

    /etc/docker/daemon.json

    { "data-root": "/mnt/docker" }
    

    Sort docker image by size

    docker images --format "{{.ID}}\t{{.Size}}\t{{.Repository}}" | sort -k 2 -h
    

    source

    Get memory, cpu usage

    docker stats --no-stream
    

    CAREFUL: Delete containers and images

    docker system prune
    

    Docker compose

    Template

    version: '3.3'
    
    services:
      vaultwarden:
        image: vaultwarden/server:xxx
        restart: unless-stopped
        volumes:
          - vaultwarden_data:/data/
        networks:
          default:
            ipv4_address: 172.1.x.2
        env_file: .env
        environment:
          - SIGNUPS_ALLOWED=false
        deploy:
          resources:
            limits:
              cpus: 2
              memory: 300M
        labels:
          - "io.parmentier.check_outdated_tag=true"
    
    volumes:
       vaultwarden_data:
    
    networks:
      default:
        driver: bridge
        ipam:
          config:
            - subnet: 172.1.x.0/24
    

    Notes:

    • ensure that an image has specific tag (if possible), avoid breaking a redeployment
    • ensure you give resources limitation (memory/cpu)
    • ensure you have a dedicated private network
    • ensure it will restart during a restart/update through unless-stopped
    • ensure you create dedicated and permanent volumes
    • encourage environment variable in .env (great if there are a lot, avoid polluting docker-compose)

    Important notice: run docker-compose up, AND NOT docker-compose create && docker-compose start that will not create the network. Dont create the network yourself, it will forget options and make trouble to attach static ips.

    CAREFUL: Delete containers and volumes

    docker-compose down -v
    

    Note: CAREFUL -v will delete the volumes

    Check if update exists (minor or patch version)

    #!/bin/bash
    # REQUIRE: awk, sed, curl
    
    # directory containing different projects and docker-compose.yml files
    LOOKING_DIRECTORY="/home/projects"
    REQUIRED_LABEL="tld.domain.check_outdated_tag=true"
    
    # NOTE it is possible to use `yq` but it requires to install a python package (not managed by package manager) and it showed to be slowest than this
    # https://stackoverflow.com/questions/5014632/how-can-i-parse-a-yaml-file-from-a-linux-shell-script
    # https://github.com/mrbaseman/parse_yaml
    function parse_yaml {
       local prefix=$2
       local s='[[:space:]]*' w='[a-zA-Z0-9_]*' fs=$(echo @|tr @ '\034')
       sed -ne "s|,$s\]$s\$|]|" \
            -e ":1;s|^\($s\)\($w\)$s:$s\[$s\(.*\)$s,$s\(.*\)$s\]|\1\2: [\3]\n\1  - \4|;t1" \
            -e "s|^\($s\)\($w\)$s:$s\[$s\(.*\)$s\]|\1\2:\n\1  - \3|;p" $1 | \
       sed -ne "s|,$s}$s\$|}|" \
            -e ":1;s|^\($s\)-$s{$s\(.*\)$s,$s\($w\)$s:$s\(.*\)$s}|\1- {\2}\n\1  \3: \4|;t1" \
            -e    "s|^\($s\)-$s{$s\(.*\)$s}|\1-\n\1  \2|;p" | \
       sed -ne "s|^\($s\):|\1|" \
            -e "s|^\($s\)-$s[\"']\(.*\)[\"']$s\$|\1$fs$fs\2|p" \
            -e "s|^\($s\)-$s\(.*\)$s\$|\1$fs$fs\2|p" \
            -e "s|^\($s\)\($w\)$s:$s[\"']\(.*\)[\"']$s\$|\1$fs\2$fs\3|p" \
            -e "s|^\($s\)\($w\)$s:$s\(.*\)$s\$|\1$fs\2$fs\3|p" | \
       awk -F$fs '{
          indent = length($1)/2;
          vname[indent] = $2;
          for (i in vname) {if (i > indent) {delete vname[i]; idx[i]=0}}
          if(length($2)== 0){  vname[indent]= ++idx[indent] };
          if (length($3) > 0) {
             vn=""; for (i=0; i<indent; i++) { vn=(vn)(vname[i])("_")}
             printf("%s%s%s=\"%s\"\n", "'$prefix'",vn, vname[indent], $3);
          }
       }'
    }
    
    function check_new_img() {
      local img="$1"
      local complete_tested_version="$2"
      local compose_file="$3"
      local email_file="$4"
    
      echo "[${img}] check if image ${complete_tested_version} exists"
    
      if docker manifest inspect ${complete_tested_version} >/dev/null 2>&1; then
        echo " -> new tag exists"
        echo " - (${compose_file} - ${img}), new tag with version ${complete_tested_version} exists" >>${email_file}
        true
        return
      fi
    
     false
    }
    
    email_file=$(mktemp)
    echo "Subject: outdated images" >${email_file}
    echo "" >>${email_file}
    
    for compose_file in $(find ${LOOKING_DIRECTORY} -name *-compose.yml); do
      echo "Checking ${compose_file}"
    
      # if you want to use `yq`, you will need something like:
      # for img in $(yq -r 'try .services[] | select(.labels[] | contains("tld.domain.check_outdated_tag=true")) | .image' ${compose_file}); do
    
      services_variables=$(parse_yaml "${compose_file}")
    
      for service_name in $(echo "${services_variables}" | grep services | cut -d_ -f2 | uniq); do
        if ! echo "${services_variables}" | grep services_${service_name}_labels | grep ${REQUIRED_LABEL} >/dev/null; then
          continue
        fi
    
        img=$(echo "${services_variables}" | grep services_${service_name}_image | cut -d= -f2 | tr -d '"')
    
        echo "[${img}] found label ${REQUIRED_LABEL}"
    
        if ! echo ${img} | grep -E "[[:digit:]]+\.[[:digit:]]+\.?[[:digit:]]*" >/dev/null; then
          echo "incapable to examine the image: ${img}"
          continue
        fi
    
        # consider a version formated as described in https://semver.org/
        version=$(echo ${img} | cut -d: -f2 | sed -nre 's/^[^0-9]*(([0-9]+\.)*[0-9]+).*/\1/p')  # NOTE https://superuser.com/questions/363865/how-to-extract-a-version-number-using-sed
    
        major_version=$(echo ${version} | cut -d. -f1)
        minor_version=$(echo ${version} | cut -d. -f2)
        patch_version=$(echo ${version} | cut -d. -f3)
    
        if [ -z "${patch_version}" ]; then
          complete_tested_version=$(echo ${img} | sed "s/${version}/${major_version}.$((minor_version+1))/")
          check_new_img "${img}" "${complete_tested_version}" "${compose_file}" "${email_file}"
        else
          complete_tested_version=$(echo ${img} | sed "s/${version}/${major_version}.${minor_version}.$((patch_version+1))/")
          check_new_img "${img}" "${complete_tested_version}" "${compose_file}" "${email_file}"
          img_exists=$?
    
          if [ "${img_exists}" != "0" ]; then
            complete_tested_version=$(echo ${img} | sed "s/${version}/${major_version}.$((minor_version+1)).0/")
            check_new_img "${img}" "${complete_tested_version}" "${compose_file}" "${email_file}"
            img_exists=$?
            if [ "${img_exists}" != "0" ]; then
              complete_tested_version=$(echo ${img} | sed "s/${version}/${major_version}.$((minor_version+1))/")
              check_new_img "${img}" "${complete_tested_version}" "${compose_file}" "${email_file}"
            fi
          fi
        fi
      done
    done
    
    lines_in_email_file=$(wc -l ${email_file} | awk '{print $1}')
    
    if [ "${lines_in_email_file}" != "0" ]; then
      curl --fail --silent --url 'smtps://smtp.gmail.com:465' --ssl-reqd --mail-from 'user@gmail.com' --mail-rcpt 'rcp@domain.tld' --upload-file ${email_file} --user 'user@gmail.com:password'
    fi
    
    rm ${email_file}
    

    See also:

    • https://www.reddit.com/r/selfhosted/comments/swzvj3/how_do_you_manage_docker_images_updates/
    • https://stackoverflow.com/questions/26423515/how-to-automatically-update-your-docker-containers-if-base-images-are-updated