Ctrl K
ComunidadGuías…

docker-compose.portainer.yml

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Notes app — Portainer stack (Synology / standalone Docker).
#
# Deploy via Portainer → Stacks → Add stack → Web editor → paste this file.
# Set the environment variables listed at the bottom in Portainer's
# "Environment variables" section — there is no .env file in a Portainer stack.
#
# Pulls prebuilt images from GHCR (private) — add ghcr.io as a Registry in
# Portainer first (Registries → Add → Custom: ghcr.io, user + read:packages
# PAT). If you already added it for another project, you're done.
# The NAS never builds anything.
#
# Ingress: web (nginx) is published on <NAS-IP>:${WEB_PORT}. Point your
# Cloudflare Tunnel / reverse proxy at HTTP → <NAS-IP>:${WEB_PORT}. TLS is
# terminated at the edge — HTTPS is required for the PWA to install on iOS.

x-logging: &default-logging
  driver: json-file
  options:
    max-size: "10m"
    max-file: "3"

services:
  db:
    image: postgres:16-alpine
    restart: unless-stopped
    logging: *default-logging
    mem_limit: 512m
    environment:
      POSTGRES_USER: ${POSTGRES_USER:-notesapp}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB:-notesapp}
    volumes:
      - ${DATA_DIR:-/volume1/docker/notabula}/pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-notesapp}"]
      interval: 10s
      timeout: 5s
      retries: 10

  api:
    image: ${IMAGE_PREFIX:-ghcr.io/jwapps-app/notabula}-api:${IMAGE_TAG:-latest}
    # Always fetch the current image on deploy — a cached stale :latest can
    # never poison a redeploy.
    pull_policy: always
    restart: unless-stopped
    logging: *default-logging
    mem_limit: 512m
    command: ["./entrypoint.sh"]
    environment:
      # Discrete parts (not a DSN): the app URL-encodes them itself, so the
      # password may contain any character (@, :, /, %, …).
      DB_HOST: db
      DB_USER: ${POSTGRES_USER:-notesapp}
      DB_PASSWORD: ${POSTGRES_PASSWORD}
      DB_NAME: ${POSTGRES_DB:-notesapp}
      APP_NAME: ${APP_NAME:-Notabula}
      APP_URL: ${APP_URL:-https://notes.example.com}
      ENVIRONMENT: production
      SESSION_TTL_DAYS: ${SESSION_TTL_DAYS:-90}
      PURGE_AFTER_DAYS: ${PURGE_AFTER_DAYS:-30}
      MAX_UPLOAD_MB: ${MAX_UPLOAD_MB:-20}
      # Native iOS push via the self-hosted push-relay. Unset = no APNs
      # (Web Push for the PWA needs no configuration either way).
      PUSH_RELAY_URL: ${PUSH_RELAY_URL:-}
      PUSH_RELAY_API_KEY: ${PUSH_RELAY_API_KEY:-}
      APNS_BUNDLE_ID: ${APNS_BUNDLE_ID:-com.jworthington.notabula}
    volumes:
      # Named volume (not a /volume1 bind mount): it inherits the image's
      # appuser ownership so the non-root API can write uploads.
      - media-data:/app/media
    # Health status is informational (visible in Portainer). Nothing gates
    # on it, so a slow boot can never fail the whole deploy.
    healthcheck:
      test:
        - CMD-SHELL
        - python -c "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://127.0.0.1:8000/health/ready',timeout=4).status==200 else 1)"
      interval: 30s
      timeout: 6s
      retries: 5
      start_period: 180s
    depends_on:
      - db

  web:
    image: ${IMAGE_PREFIX:-ghcr.io/jwapps-app/notabula}-web:${IMAGE_TAG:-latest}
    pull_policy: always
    restart: unless-stopped
    logging: *default-logging
    mem_limit: 64m
    volumes:
      - media-data:/srv/media:ro
    ports:
      - "${WEB_PORT:-8210}:80"
    depends_on:
      - api

  # Nightly backup into ${BACKUP_DIR}, keeping the last 14 days. Backs up the
  # Postgres dump AND the uploaded attachments — a DB-only backup would
  # restore with every image in every note broken.
  backup:
    image: postgres:16-alpine
    restart: unless-stopped
    logging: *default-logging
    mem_limit: 128m
    environment:
      POSTGRES_USER: ${POSTGRES_USER:-notesapp}
      POSTGRES_DB: ${POSTGRES_DB:-notesapp}
      PGPASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - ${BACKUP_DIR:-/volume1/Backup/notabula}:/backup
      - media-data:/media:ro
    entrypoint:
      - /bin/sh
      - -c
      - |
        while true; do
          ts=$$(date +%Y%m%d-%H%M%S)
          if pg_dump -h db -U $$POSTGRES_USER -d $$POSTGRES_DB -F c -f /backup/db-$$ts.dump &&
             tar -czf /backup/media-$$ts.tar.gz -C /media .; then
            echo "backup $$ts done"
            find /backup -name '*.dump' -mtime +14 -delete
            find /backup -name '*.tar.gz' -mtime +14 -delete
            sleep 86400
          else
            echo "backup failed (db not ready?); retrying in 5m"
            rm -f /backup/db-$$ts.dump /backup/media-$$ts.tar.gz
            sleep 300
          fi
        done
    depends_on:
      - db

volumes:
  media-data:

# ---------------------------------------------------------------------------
# Environment variables to set in Portainer (Stacks → Environment variables):
#
#   POSTGRES_PASSWORD   (required) strong password for Postgres
#   WEB_PORT            host port for the app          (default 8210)
#   APP_URL             public URL, display only       (default https://notes.example.com)
#   DATA_DIR            host dir for Postgres data     (default /volume1/docker/notabula)
#   BACKUP_DIR          host dir for nightly backups   (default /volume1/Backup/notabula)
#   IMAGE_PREFIX        (default ghcr.io/jwapps-app/notabula)
#   IMAGE_TAG           (default latest)
#   PUSH_RELAY_URL      APNs relay for the native iOS app (e.g. http://<NAS-IP>:8088)
#   PUSH_RELAY_API_KEY  per-app key minted on the relay
#   APNS_BUNDLE_ID      (default com.jworthington.notabula)
# ---------------------------------------------------------------------------