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.
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,