# seafile — file sync service on ana-docker, fronted by Traefik at # seafile.phasefinal.com. # # ⚠️ RESTART POLICY IS LOAD-BEARING HERE. None of these three services # declared one until 2026-08-18, which means Docker defaulted them to `no`. # On 2026-05-06T21:27:45Z the daemon stopped all three within 200ms of each # other — a daemon restart or host reboot — and because the policy was `no`, # nothing brought them back. Seafile then sat dead for three months and the # only trace was an EXITED card on the dashboard that nobody read as an # outage. The exit code was 255 on all three, which is just what a container # that ignores SIGTERM reports when the daemon stops it; it is NOT evidence # of a crash, and reading it as one sends you looking for a bug that does # not exist. `unless-stopped` on all three is the fix. # # Data lives in two local named volumes (`seafile_seafile_db`, # `seafile_seafile_datastore`) — NOT on ana-nas NFS, so this stack is not # exposed to the NAS SPOF that affects postgres/rest-server/PBS. services: db: image: mariadb:10.6 container_name: seafile-mysql restart: unless-stopped environment: - MYSQL_ROOT_PASSWORD=${DB_ROOT_PW} - MYSQL_LOG_CONSOLE=true volumes: - seafile_db:/var/lib/mysql # Requested, specifies the path to MySQL data persistent store. networks: - tnet # Required so seafile can wait for InnoDB to initialize before # starting seahub. Without it, after a daemon restart seahub races # mysql, hits "Connection refused", and the container's start.py # gives up — leaving nginx serving seafile but proxying to a dead # python backend. Image ships /usr/local/bin/healthcheck.sh. healthcheck: test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] interval: 5s timeout: 5s retries: 30 start_period: 30s memcached: image: memcached:1.6.18 container_name: seafile-memcached restart: unless-stopped entrypoint: memcached -m 256 networks: - tnet seafile: image: seafileltd/seafile-mc:11.0-latest container_name: seafile restart: unless-stopped ports: - 9180:80 volumes: - seafile_datastore:/shared # Requested, specifies the path to Seafile data persistent store. environment: - DB_HOST=db - DB_ROOT_PASSWD=${DB_ROOT_PW} - TIME_ZONE=America/Los_Angeles - SEAFILE_ADMIN_EMAIL=${SEAFILE_ADMIN_EMAIL} - SEAFILE_ADMIN_PASSWORD=${SEAFILE_ADMIN_PW} # Long-form depends_on with condition: service_healthy on db. # Compose waits for the mariadb healthcheck above to pass before # starting this container, so seahub never sees the # "Connection refused" race that wedges it on daemon restart. depends_on: db: condition: service_healthy memcached: condition: service_started labels: - homepage.group=Apps - homepage.name=SeaFile - homepage.icon=mdi-sync-circle - homepage.description=File Sync Service (ana) - homepage.href=https://seafile.phasefinal.com - traefik.enable=true - traefik.http.routers.seafile.tls=true - traefik.http.routers.seafile.rule=Host(`seafile.phasefinal.com`) - traefik.http.routers.seafile.tls.certresolver=anaprod networks: - tnet env_file: - .env volumes: seafile_db: null seafile_datastore: null networks: tnet: name: traefik-net external: true