# {{ ansible_managed }} # Dawarich — self-hosted location history. Four containers + watchtower: # dawarich_db postgis/postgis:16-3.4-alpine (PostGIS-enabled Postgres) # dawarich_redis redis:7-alpine (cache + Sidekiq queue) # dawarich_app freikin/dawarich (Rails web tier, port 3000) # dawarich_sidekiq freikin/dawarich (background jobs) # DB lives here (not the central DB VM) because the central image is plain # postgres:16-alpine without the postgis extension. See memory project_dawarich. services: dawarich_db: image: "{{ postgis_image }}" container_name: dawarich_db restart: unless-stopped shm_size: 1g environment: POSTGRES_USER: "{{ dawarich_db_user }}" POSTGRES_PASSWORD: "{{ dawarich_db_password }}" POSTGRES_DB: "{{ dawarich_db_name }}" volumes: - /opt/dawarich/postgres:/var/lib/postgresql/data # No published ports — only the app + sidekiq reach it over the compose net. healthcheck: test: ["CMD-SHELL", "pg_isready -U {{ dawarich_db_user }} -d {{ dawarich_db_name }}"] interval: 15s timeout: 5s retries: 10 start_period: 30s dawarich_redis: image: "{{ redis_image }}" container_name: dawarich_redis restart: unless-stopped volumes: - /opt/dawarich/redis:/data # No published ports — internal-only. healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 15s timeout: 3s retries: 5 dawarich_app: image: "{{ dawarich_image }}" container_name: dawarich_app restart: unless-stopped depends_on: dawarich_db: condition: service_healthy dawarich_redis: condition: service_healthy ports: - "{{ dawarich_port }}:3000" volumes: - /opt/dawarich/public:/var/app/public - /opt/dawarich/watched:/var/app/tmp/imports/watched - /opt/dawarich/storage:/var/app/storage # Solid_cache + solid_queue + solid_cable SQLite files. Without this # bind, the queue DB is ephemeral and in-flight Sidekiq jobs are lost # on container recreate. Path matches the entrypoint's expectation. - /opt/dawarich/db_data:/dawarich_db_data # The image ships a `web-entrypoint.sh` that runs `db:prepare` (creates # the solid_cache + solid_queue SQLite files in /var/app/storage, runs # Postgres migrations) before exec'ing the command. We MUST use it — # without it, Rails 8 boots fail with "No database file specified" on # solid_cache's first lookup. The image default entrypoint is `bundle exec` # alone, which skips the prep. entrypoint: ["/usr/local/bin/web-entrypoint.sh"] command: ["bin/rails", "server", "-p", "3000", "-b", "::"] environment: RAILS_ENV: production SELF_HOSTED: "true" # localhost + ::1 needed for Docker healthcheck (Rails 8 host # authorization rejects any host not in this list, including loopback). APPLICATION_HOSTS: "{{ dawarich_domain }},localhost,127.0.0.1,::1" APPLICATION_PROTOCOL: "https" DOMAIN: "{{ dawarich_domain }}" TIME_ZONE: "{{ timezone }}" DISTANCE_UNIT: "km" # Database DATABASE_HOST: dawarich_db DATABASE_PORT: "5432" DATABASE_USERNAME: "{{ dawarich_db_user }}" DATABASE_PASSWORD: "{{ dawarich_db_password }}" DATABASE_NAME: "{{ dawarich_db_name }}" RAILS_MAX_THREADS: "5" # Redis REDIS_URL: "redis://dawarich_redis:6379" # Secrets SECRET_KEY_BASE: "{{ dawarich_secret_key_base }}" OTP_ENCRYPTION_PRIMARY_KEY: "{{ dawarich_otp_primary_key }}" OTP_ENCRYPTION_DETERMINISTIC_KEY: "{{ dawarich_otp_deterministic_key }}" OTP_ENCRYPTION_KEY_DERIVATION_SALT: "{{ dawarich_otp_salt }}" # Registration policy — OIDC-only when enabled. Anything else and a # local user form silently appears on /users/sign_up. ALLOW_EMAIL_PASSWORD_REGISTRATION: "{{ dawarich_allow_email_password_registration | string | lower }}" {% if dawarich_oidc_enabled %} # ---- Auth: Authentik OIDC ---- OIDC_CLIENT_ID: "{{ dawarich_oidc_client_id }}" OIDC_CLIENT_SECRET: "{{ dawarich_oidc_client_secret }}" OIDC_ISSUER: "{{ dawarich_oidc_issuer }}" OIDC_REDIRECT_URI: "{{ dawarich_oidc_redirect_uri }}" OIDC_PROVIDER_NAME: "{{ dawarich_oidc_provider_name }}" OIDC_AUTO_REGISTER: "{{ dawarich_oidc_auto_register | string | lower }}" OIDC_PKCE_ENABLED: "true" {% endif %} # TCP socket check, not HTTP. Rails 8 force_ssl (APPLICATION_PROTOCOL=https) # redirects every plain-HTTP path to https — curl/wget can't satisfy a 2xx # without TLS, and Puma is plain-HTTP (Caddy terminates TLS upstream). The # socket check confirms Puma is listening, which is what we actually care # about for orchestration. healthcheck: test: ["CMD-SHELL", "ruby -rsocket -e 'TCPSocket.new(\"localhost\",3000).close' || exit 1"] interval: 30s timeout: 10s retries: 5 start_period: 120s dawarich_sidekiq: image: "{{ dawarich_image }}" container_name: dawarich_sidekiq restart: unless-stopped depends_on: dawarich_db: condition: service_healthy dawarich_redis: condition: service_healthy dawarich_app: condition: service_started volumes: - /opt/dawarich/public:/var/app/public - /opt/dawarich/watched:/var/app/tmp/imports/watched - /opt/dawarich/storage:/var/app/storage # Solid_cache + solid_queue + solid_cable SQLite files. Without this # bind, the queue DB is ephemeral and in-flight Sidekiq jobs are lost # on container recreate. Path matches the entrypoint's expectation. - /opt/dawarich/db_data:/dawarich_db_data # Same entrypoint pattern as the web tier — sidekiq's script ensures the # DB is migrated before workers start consuming jobs. entrypoint: ["/usr/local/bin/sidekiq-entrypoint.sh"] command: ["sidekiq"] environment: RAILS_ENV: production SELF_HOSTED: "true" # localhost + ::1 needed for Docker healthcheck (Rails 8 host # authorization rejects any host not in this list, including loopback). APPLICATION_HOSTS: "{{ dawarich_domain }},localhost,127.0.0.1,::1" APPLICATION_PROTOCOL: "https" DOMAIN: "{{ dawarich_domain }}" TIME_ZONE: "{{ timezone }}" DATABASE_HOST: dawarich_db DATABASE_PORT: "5432" DATABASE_USERNAME: "{{ dawarich_db_user }}" DATABASE_PASSWORD: "{{ dawarich_db_password }}" DATABASE_NAME: "{{ dawarich_db_name }}" REDIS_URL: "redis://dawarich_redis:6379" SECRET_KEY_BASE: "{{ dawarich_secret_key_base }}" OTP_ENCRYPTION_PRIMARY_KEY: "{{ dawarich_otp_primary_key }}" OTP_ENCRYPTION_DETERMINISTIC_KEY: "{{ dawarich_otp_deterministic_key }}" OTP_ENCRYPTION_KEY_DERIVATION_SALT: "{{ dawarich_otp_salt }}" BACKGROUND_PROCESSING_CONCURRENCY: "{{ dawarich_sidekiq_concurrency }}" watchtower: container_name: dawarich_watchtower image: containrrr/watchtower:latest restart: unless-stopped dns: - 192.168.1.1 ports: - "8088:8080" volumes: - /var/run/docker.sock:/var/run/docker.sock environment: - DOCKER_API_VERSION=1.40 - WATCHTOWER_HTTP_API_UPDATE=true - WATCHTOWER_HTTP_API_TOKEN={{ watchtower_api_token }} - TZ={{ timezone }} - WATCHTOWER_CLEANUP=true - WATCHTOWER_INCLUDE_RESTARTING=true - WATCHTOWER_NO_STARTUP_MESSAGE=true - WATCHTOWER_NOTIFICATION_REPORT=true - WATCHTOWER_NOTIFICATIONS=shoutrrr - WATCHTOWER_NOTIFICATION_URL={{ watchtower_gotify_url }} - WATCHTOWER_NOTIFICATIONS_HOSTNAME=DAWARICH - WATCHTOWER_NOTIFICATION_TITLE_TAG=DAWARICH # Render empty when nothing was updated/failed — see feedback_watchtower_pushover. - 'WATCHTOWER_NOTIFICATION_TEMPLATE={% raw %}{{- if .Report -}}{{- with .Report -}}{{- if or .Updated .Failed -}}{{ len .Updated }} updated, {{ len .Failed }} failed{{- range .Updated}} | updated {{.Name}}{{- end -}}{{- range .Failed}} | FAILED {{.Name}}{{- end -}}{{- end -}}{{- end -}}{{- end -}}{% endraw %}'