748 - Docker PRO. Cómo Optimizar Gitea y Stalwart con depends_on y Healthchecks

748 - Docker PRO. Cómo Optimizar Gitea y Stalwart con depends_on y Healthchecks

Docker PRO: Optimiza Gitea y Stalwart con la gestión avanzada de depends_on y healthchecks. Evita fallos de inicio en tus servicios Docker Compose.

1:25
-3:15

En las últimas semanas he estado probando nuevos servicios docker y actualizando algunos de los que vengo utilizando durante años con el objetivo de implantar nuevas y mejores prácticas. Uno de los servicios que te traigo en este episodio es Dockpeek, una de esas herramientas que tanto me gustan y que reemplazan algunas de las características de Portainer. Y por otro lado, he actualizado tanto los compose.yml de Quantum, Gitea como el de Stalwart, el servidor de correo.

Docker PRO. Cómo Optimizar Gitea y Stalwart con depends_on y Healthchecks

Actualizando servicios

En el episodio 730 de esta misma temporada te hablé de algunas de las mejores prácticas que utilizo en mis servicios Docker. Desde entonces he estado actualizando algunos de los servicios que vengo utilizando desde hace años, como es el caso de Gitea, el servicio de alojamiento de repositorios Git, y Stalwart, el servidor de correo. En ambos casos he actualizado los archivos compose.yml para adaptarlos a las nuevas prácticas.

Actualizando Quantum

No me había fijado pero Quantum, el servicio que utilizo para compartir archivos, como por ejemplo los libros, permite utilizar un usuario que no es root. Entiendo que esto viene del original, de Filebrowser, pero lo cierto es que no me había dado cuenta de ello. Así que decidí a cambiarlo y lo dejé así. Sin embargo, hace unos días en una actualización dejó de funcionar. Así que estuve realizando distintas pruebas hasta que di con el problema, y como no podía ser de otra manera, fue cuestión de permisos.

La configuración y dado que actualmente utilizo Dockge, la tengo en el propio compose.yml, tal y como comenté en el episodio 730 titulado lo que no te han contado de las configuraciones en Docker. Y es algo similar a lo que te muestro a continuación,

services:
  quantum:
    image: gtstef/filebrowser
    container_name: quantum
    restart: unless-stopped
    init: true
    environment:
      TZ: Europe/Madrid
    configs:
      - source: quantum
        target: /home/filebrowser/config.yaml
        uid: "1000"
        gid: "1000"
        mode: "0600"
    volumes:
      - data:/home/filebrowser/data
      - share:/share
      - srv:/srv
    user: filebrowser
    networks:
      - proxy
    labels:
      traefik.enable: true
      traefik.http.services.quantum.loadbalancer.server.port: 80
      traefik.http.routers.quantum.entrypoints: https
      traefik.http.routers.quantum.rule: Host(`${FQDN}`)
volumes:
  share: {}
  data: {}
  srv: {}
networks:
  proxy:
    external: true
configs:
  quantum:
    content: |
      server:
        port: 80
        sources:
          - path: "/share"
            config:
              defaultEnabled: true
          - path: "/srv"
            config:
              defaultEnabled: true
      auth:
        methods:
          password:
            enabled: false
          oidc:
            enabled: true
            clientId: ${POCKETID_CLIENT}
            clientSecret: ${POCKETID_SECRET}
            issuerUrl: ${POCKETID_URL}
            scopes: email openid profile groups
            userIdentifier: preferred_username
            disableVerifyTLS: false
            logoutRedirectUrl: ""
            createUser: true
            adminGroup: quantum

Si te fijas he realizado básicamente tres cambios en la parte de los configs,

configs:
  - source: quantum
    target: /home/filebrowser/config.yaml
    uid: "1000"
    gid: "1000"
    mode: "0600"

Por un lado, he apuntado el target a la ubicación donde se encuentra por defecto la configuración de Quantum. Y por otro lado, he indicado cual es el uid y gid del propietario del archivo. Además he cambiado el modo para que sea de lectura y escritura.

Actualizando Gitea

El primero de los cambios que he introducido en el compose de Gitea, lo mismo que hice en tantos otros, ha sido el inicio condicional. En lugar de que cada uno de los servicios se inicie automáticamente, he configurado el servicio de Gitea para que espere a que el servicio de base de datos esté operativo antes de iniciar. Esto se consigue utilizando la opción depends_on junto con una condición de salud (condition: service_healthy). De esta manera, Gitea solo se iniciará cuando la base de datos esté lista para aceptar conexiones.

Esto mismo lo hice en el servicio que se encarga de hacer las copias de seguridad, de manera, que como es de esperar, hasta que la base de datos no se ha iniciado y se ha iniciado correctamente, no se inician los servicios que dependen de ella.

Otro detalle importante es el runner. Ahora tengo la configuración es algo como GITEA_INSTANCE_URL: 'http://gitea:3000'. Antes apuntaba a la URL pública, pero ahora apunta al nombre del servicio dentro de la red interna de Docker. Esto es más seguro y eficiente, ya que evita la necesidad de salir a Internet para acceder al servicio. Pero no solo esto, sino que además le quito trabajo a Traefik, ya que no tiene que gestionar este tráfico interno.

services:
  gitea:
    image: gitea/gitea:1.24.6
    container_name: gitea
    environment:
      - USER_UID=1000
      - USER_GID=1000
      - GITEA__database__DB_TYPE=postgres
      - GITEA__database__HOST=db:5432
      - GITEA__database__NAME=gitea
      - GITEA__database__USER=gitea
      - GITEA__database__PASSWD=gitea
      - GITEA__repository__DEFAULT_BRANCH=main
      - GITEA__repository__ENABLE_PUSH_CREATE_ORG=true
      - GITEA__repository__ENABLE_PUSH_CREATE_USER=true
      - GITEA__server__OFFLINE_MODE=true
      - GITEA__server__DOMAIN=${FQDN}
      - GITEA__server__SSH_DOMAIN=${FQDN}
      - GITEA__server__ROOT_URL=https://${FQDN}
      - GITEA__server__DISABLE_SSH=false
      - GITEA__server__SSH_PORT=2222
      - GITEA__server__SSH_LISTEN_PORT=22
      - GITEA__service__DISABLE_REGISTRATION=true
      - GITEA__service__REQUIRE_SIGNIN_VIEW=true
      - GITEA__service__DEFAULT_ALLOW_CREATE_ORGANIZATION=false
    restart: always
    networks:
      - internal
      - proxy
    volumes:
      - ./data/gitea:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    depends_on:
      db:
        condition: service_healthy
        restart: true
    healthcheck:
      test: curl -fSs http://gitea:3000/api/healthz
      start_period: 60s
      interval: 60s
      timeout: 20s
      retries: 3
    labels:
      - traefik.enable=true
      - traefik.http.services.gitea.loadbalancer.server.port=3000
      - traefik.http.routers.gitea-secure.entrypoints=https
      - traefik.http.routers.gitea-secure.rule=Host(`${FQDN}`)
      - traefik.tcp.routers.gitea-ssh.entrypoints=git
      - traefik.tcp.routers.gitea-ssh.rule=HostSNI(`*`)
      - traefik.tcp.services.gitea-ssh.loadbalancer.server.port=22

  db:
    image: postgres:17-alpine
    restart: always
    environment:
      - POSTGRES_USER=gitea
      - POSTGRES_PASSWORD=xxxxxxxxxxxxx
      - POSTGRES_DB=gitea
    networks:
      - internal
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD", "pg_isready", "-d", "gitea", "-U", "gitea"]
      start_period: 120s
      interval: 60s
      timeout: 10s
      retries: 3

  gitea_act_runner:
    image: gitea/act_runner:latest
    container_name: gitea_act_runner
    restart: unless-stopped
    networks:
      - internal
    volumes:
      -  /var/run/docker.sock:/var/run/docker.sock:rw
      - ./data:/data:rw
      - ./config.yaml:/config.yaml
    depends_on:
      gitea:
        condition: service_healthy
        restart: true
    environment:
      TZ: "Europe/Madrid"
      GITEA_INSTANCE_URL: 'http://gitea:3000' # required
      GITEA_RUNNER_REGISTRATION_TOKEN: 'XXXXXXXXXXXXXXXXXXXX'
      GITEA_RUNNER_NAME: "gitea_act_runner"
      GITEA_RUNNER_LABELS: "my-runner"
      CONFIG_FILE: /config.yaml

  backup:
    image: atareao/postgres-backup:latest
    container_name: backup_postgres
    init: true
    restart: unless-stopped
    networks:
      - internal
    volumes:
      - ./hooks:/hooks
      - ./backup:/backup
    environment:
      POSTGRESQL_DB: gitea
      POSTGRESQL_HOST: db
      POSTGRESQL_USER: gitea
      POSTGRESQL_PASSWORD: gitea
      SCHEDULE: '0 0 9 * * * *'
      BACKUP_KEEP_MINS: 1440
      BACKUP_KEEP_DAYS: 7
      BACKUP_KEEP_WEEKS: 4
      BACKUP_KEEP_MONTHS: 6
    depends_on:
      db:
        condition: service_healthy
        restart: true


volumes:
  postgres_data: {}

networks:
  internal:
  proxy:
    external: true

En el caso de Gitea todavía tengo trabajo por hacer, como por ejemplo la cuestión de utilizar volúmenes Docker en lugar de volúmenes montados desde el sistema de archivos del host. Pero poco a poco.

Stalwart, mi cliente de correo

En el caso particular de Stalwart, he realizado dos operaciones. Lo primero ha sido cambiar la base de datos que utilizaba de RocketDB a PostgreSQL.

La razón para hacerlo así es que hasta el momento RocketDB me ha parecido muy opaca. Probablemente porque no le he dedicado el tiempo necesario, pero he preferido ir a un viejo conocido, aunque muy sobredimensionado para el uso que seguramente le voy a dar.

Otra razón para el cambio ha sido que con PostgreSQL me resulta realmente sencillo poder hacer copias de seguridad de la base de datos. Para esto hace ya algún tiempo cree un sencillo contenedor que se encargaba de forma eficiente de realizar las copias de seguridad de las bases de datos PostgreSQL y MariaDB/MySQL. Así que he aprovechado este contenedor para hacer las copias de seguridad de la base de datos de Stalwart.

services:
  stalwart:
    image: stalwartlabs/stalwart:latest
    container_name: stalwart
    restart: unless-stopped
    init: true
    hostname: mail.tuservidor.es
    security_opt:
      - no-new-privileges:true
    volumes:
      - /home/lorenzo/docker/dockge/stacks/mail/config.toml:/opt/stalwart/etc/config.toml
      - /etc/localtime:/etc/localtime:ro
      - data:/opt/stalwart
      - certs:/data/certs:ro
      - logs:/opt/stalwart/logs
    depends_on:
      stalwart_db:
        condition: service_healthy
        restart: true
    networks:
      proxy:
        ipv4_address: 172.29.0.41
      internal: {}
    labels:
      - traefik.enable=true
      # admin ui
      - traefik.http.routers.stalwart.rule=Host(`mail.tuservidor.es`) ||
        Host(`autodiscover.tuservidor.es`) ||
        Host(`autoconfig.tuservidor.es`) ||
        Host(`mta-sts.tuservidor.es`)
      - traefik.http.routers.stalwart.entrypoints=https
      - traefik.http.routers.stalwart.service=stalwart
      - traefik.http.services.stalwart.loadbalancer.server.port=8080
      # smtp
      - traefik.tcp.routers.stalwart-smtp.rule=HostSNI(`*`)
      - traefik.tcp.routers.stalwart-smtp.entrypoints=smtp
      - traefik.tcp.routers.stalwart-smtp.service=stalwart-smtp
      - traefik.tcp.services.stalwart-smtp.loadbalancer.server.port=25
      - traefik.tcp.services.stalwart-smtp.loadbalancer.proxyProtocol.version=2
      # jmap
      - traefik.tcp.routers.stalwart-jmap.rule=HostSNI(`*`)
      - traefik.tcp.routers.stalwart-jmap.entrypoints=https
      - traefik.tcp.routers.stalwart-jmap.tls.passthrough=true
      - traefik.tcp.routers.stalwart-jmap.service=stalwart-jmap
      - traefik.tcp.services.stalwart-jmap.loadbalancer.server.port=443
      - traefik.tcp.services.stalwart-jmap.loadbalancer.proxyProtocol.version=2
      # esmtp
      - traefik.tcp.routers.stalwart-smtps.rule=HostSNI(`*`)
      - traefik.tcp.routers.stalwart-smtps.entrypoints=smtps
      - traefik.tcp.routers.stalwart-smtps.tls.passthrough=true
      - traefik.tcp.routers.stalwart-smtps.service=stalwart-smtps
      - traefik.tcp.services.stalwart-smtps.loadbalancer.server.port=465
      - traefik.tcp.services.stalwart-smtps.loadbalancer.proxyProtocol.version=2
      # imap-ssl
      - traefik.tcp.routers.stalwart-imaps.rule=HostSNI(`*`)
      - traefik.tcp.routers.stalwart-imaps.entrypoints=imaps
      - traefik.tcp.routers.stalwart-imaps.tls.passthrough=true
      - traefik.tcp.routers.stalwart-imaps.service=stalwart-imaps
      - traefik.tcp.services.stalwart-imaps.loadbalancer.server.port=993
      - traefik.tcp.services.stalwart-imaps.loadbalancer.proxyProtocol.version=2
  stalwart_db:
    image: postgres
    container_name: stalwart_db
    restart: unless-stopped
    init: true
    environment:
      POSTGRES_USER: stalwart
      POSTGRES_PASSWORD: stalwart_password
      POSTGRES_DB: stalwart
      PGUSER: stalwart
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test:
        - CMD-SHELL
        - sh -c 'pg_isready -U stalwart -d stalwart'
      interval: 10s
      timeout: 5s
      retries: 10
    networks:
      - internal
  backup:
    image: atareao/postgres-backup:latest
    container_name: backup_stalwart
    init: true
    restart: unless-stopped
    networks:
      - internal
    volumes:
      - backup_hooks:/hooks
      - backup_data:/backup
    environment:
      POSTGRESQL_DB: stalwart
      POSTGRESQL_HOST: stalwart_db
      POSTGRESQL_USER: stalwart
      POSTGRESQL_PASSWORD: stalwart_password
      SCHEDULE: 0 0 9 * * * *
      BACKUP_KEEP_MINS: 1440
      BACKUP_KEEP_DAYS: 7
      BACKUP_KEEP_WEEKS: 4
      BACKUP_KEEP_MONTHS: 6
    depends_on:
      stalwart_db:
        condition: service_healthy
        restart: true
networks:
  internal: {}
  proxy:
    external: true
volumes:
  backup_hooks: {}
  backup_data: {}
  data: {}
  logs: {}
  pgdata: {}
  certs:
    external: true

Dockpeek

Dockpeek es un panel de control ligero y autoalojado para Docker que permite un acceso rápido y la gestión de contenedores Docker en múltiples hosts.

Características:

  • Acceso web con un solo clic.
  • Mapeo automático de puertos.
  • Registros de contenedores en vivo.
  • Integración con Traefik.
  • Comprobación de actualizaciones de imágenes.

Instalación

La instalación se realiza generalmente utilizando Docker Compose, con configuraciones disponibles para una configuración básica, seguridad mejorada a través de un proxy de socket y entornos multi-host. En mi caso particular, la configuración que tengo es como te muestro a continuación,

  services:
    dockpeek:
      image: ghcr.io/dockpeek/dockpeek:latest
      container_name: dockpeek
      restart: unless-stopped
      init: true
      env_file:
        - .env
      volumes:
        - /var/run/docker.sock:/var/run/docker.sock
      networks:
        - proxy
      labels:
        traefik.enable: true
        traefik.http.services.dockpeek.loadbalancer.server.port: 8000
        traefik.http.routers.dockpeek.entrypoints: https
        traefik.http.routers.dockpeek.rule: Host(`${FQDN}`)
  networks:
    proxy:
      external: true
  x-dockge:
    urls:
      - "https://${FQDN}"

Más información,

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *