Nie wieder latest. Was unser Dawarich-Upgrade lehrte - und wie wir stabil bleiben

Nie wieder latest: Unsere Dawarich-Odyssee auf Synology/Portainer

Nie wieder latest: Unsere Dawarich-Odyssee auf Synology/Portainer

Kurzfassung:
Ein Upgrade auf freikin/dawarich:0.33.x führte bei uns zu Asset-/Startproblemen (Sprockets/Webmanifest, gemountete Assets, fehlender Start-Command).
Das Rollback auf 0.32.0 mit fix gesetztem Command und ohne Asset-Mounts war sofort stabil.
Merke: In Produktion niemals latest verwenden.


Ausgangslage

  • Plattform: Synology NAS
  • Orchestrierung: Portainer (Docker Compose)
  • Services: PostGIS (PostgreSQL), Redis, Rails (Dawarich), Sidekiq
  • Ziel: Update von stabil laufender 0.32.x auf 0.33.x

Symptome & Fehlerbilder

  1. Asset-Build bricht ab (Sprockets/Webmanifest):
    Sprockets::Rails::Helper::AssetNotFound: The asset "favicon/android-chrome-192x192.png" is not present
    Errno::EEXIST: File exists @ dir_s_mkdir - /var/app/public/assets/channels
  2. Mount blockiert Assets:
    rm: cannot remove 'public/assets/channels': Device or resource busy
  3. Nicht vorhandene Rake/Build-Tasks:
    Unrecognized command "tailwind:build"
  4. Container startet nicht / sofortiger Abbruch:
    bundler: exec needs a command to run
    In Portainer: „No log line matching the “ filter“ (der Prozess war sofort beendet).
  5. Weitere Stolpersteine:
    Fehlender Host-Ordner für Logs → Bind-Mount Fehler.

Die Ursachen in Klartext

  • Gemountete public/assets (oder Unterordner) verhindern, dass Sprockets schreiben/aufräumen darf → EEXIST/Busy.
  • Webmanifest-Rake-Task erwartet Favicons im Asset-Pfad. Fehlen die Dateien, bricht die Precompilation ab.
  • Entrypoint + Bundler: Das Image ruft intern bundle exec "$@" auf. Ohne expliziten Command endet es mit:
    bundler: exec needs a command to run.

So läuft’s stabil (Rollback auf 0.32.0)

Bewährter Compose-Ausschnitt (anonymisiert):

version: "3.9"

services:
  db:
    image: postgis/postgis:17-3.5-alpine
    container_name: app-db
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
      POSTGRES_DB: app_production
      TZ: Europe/Berlin
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres -d app_production"]
      interval: 10s
      retries: 5
      start_period: 30s
    volumes:
      - /path/to/app/db:/var/lib/postgresql/data
    restart: unless-stopped
    networks: [appnet]

  redis:
    image: redis:7.4-alpine
    container_name: app-redis
    command: ["redis-server","--save","","--appendonly","no"]
    healthcheck:
      test: ["CMD-SHELL", "redis-cli ping || exit 1"]
    volumes:
      - /path/to/app/redis:/data
    restart: unless-stopped
    networks: [appnet]

  web:
    image: freikin/dawarich:0.32.0
    container_name: app-web
    # WICHTIG: Command explizit setzen
    command: ["bin/rails","server","-p","3000","-b","0.0.0.0"]
    environment:
      RAILS_ENV: production
      RAILS_SERVE_STATIC_FILES: "true"
      RAILS_LOG_TO_STDOUT: "true"
      REDIS_URL: redis://redis:6379
      DATABASE_HOST: db
      DATABASE_USERNAME: postgres
      DATABASE_PASSWORD: ${DATABASE_PASSWORD}
      DATABASE_NAME: app_production
      SECRET_KEY_BASE: ${SECRET_KEY_BASE}
      APPLICATION_HOSTS: app.example.com
      TIME_ZONE: Europe/Berlin
      SELF_HOSTED: "true"
      STORE_GEODATA: "true"
      MAP_TILES_URL: "https://tile.openstreetmap.org/{z}/{x}/{y}.png"
      MAP_TILES_ATTRIBUTION: "© OpenStreetMap contributors"
    volumes:
      - /path/to/app/watched:/var/app/tmp/imports/watched
      - /path/to/app/storage:/var/app/storage
      - /path/to/app/logs:/var/app/log
    ports:
      - "3639:3000"
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    restart: unless-stopped
    networks: [appnet]

  sidekiq:
    image: freikin/dawarich:0.32.0
    container_name: app-sidekiq
    command: ["sidekiq"]
    environment:
      RAILS_ENV: production
      RAILS_LOG_TO_STDOUT: "true"
      REDIS_URL: redis://redis:6379
      DATABASE_HOST: db
      DATABASE_USERNAME: postgres
      DATABASE_PASSWORD: ${DATABASE_PASSWORD}
      DATABASE_NAME: app_production
      SECRET_KEY_BASE: ${SECRET_KEY_BASE}
      BACKGROUND_PROCESSING_CONCURRENCY: 10
      SELF_HOSTED: "true"
      STORE_GEODATA: "true"
    volumes:
      - /path/to/app/watched:/var/app/tmp/imports/watched
      - /path/to/app/storage:/var/app/storage
      - /path/to/app/logs:/var/app/log
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    restart: unless-stopped
    networks: [appnet]

networks:
  appnet:
    driver: bridge

Hinweise:

  • Keine Mounts auf public/assets, public/assets/channels oder public/manifest.json.
  • Host-Ordner /path/to/app/logs vorher anlegen, sonst scheitert der Bind-Mount.
  • ${DATABASE_PASSWORD} und ${SECRET_KEY_BASE} kommen aus einer .env oder Portainer-UI (keine Secrets im Compose!).

Checkliste: sichere Deploys in Produktion

  1. Niemals latest verwenden. Immer konkrete Tags pinnen (z. B. 0.32.0).
  2. Start-Commands explizit setzen (Rails/Sidekiq), Entrypoint nicht überschreiben.
  3. Keine Asset-Mounts (Sprockets braucht Schreibrechte bei Precompile).
  4. Host-Verzeichnisse anlegen, bevor man sie bind-mountet (storage, watched, logs).
  5. Healthchecks/depends_on nutzen: DB/Redis erst „healthy“, dann App/Sidekiq.
  6. Logs richtig deuten: „No log line …“ in Portainer heißt oft: Prozess sofort beendet.

Upgrade-Plan (wenn 0.33.x reif ist)

  • Changelog/Issues prüfen (bes. Webmanifest/Favicons, Asset-Build).
  • Staging-Test mit identischem Compose (separates Netzwerk/Port).
  • Vorher DB-Backup ziehen (Dump).
  • Commands beibehalten, keine Asset-Mounts.
  • Rollback-Pfad parat halten (Tags schnell wechselbar).

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert