services.yml

---
sso:
  bond: 1
  enabled: "{{ 'web-app-keycloak' in group_names }}"
  shared: "{{ 'web-app-keycloak' in group_names }}"
  flavor: oidc
  oidc:
    plugin: ""
# nocheck: playwright-service-flag: infrastructural sub-component (TURN/STUN)
coturn:
  bond: 1
  enabled: "{{ 'web-svc-coturn' in group_names }}"
  shared: "{{ 'web-svc-coturn' in group_names }}"
logout:
  bond: 1
  enabled: "{{ 'web-svc-logout' in group_names }}"
  shared: "{{ 'web-svc-logout' in group_names }}"
ldap:
  bond: 1
  enabled: "{{ 'svc-db-openldap' in group_names }}"
  shared: "{{ 'svc-db-openldap' in group_names }}"
dashboard:
  bond: 1
  enabled: "{{ 'web-app-dashboard' in group_names }}"
  shared: "{{ 'web-app-dashboard' in group_names }}"
matomo:
  bond: 1
  enabled: "{{ 'web-app-matomo' in group_names }}"
  shared: "{{ 'web-app-matomo' in group_names }}"
email:
  bond: 1
  enabled: "{{ MAIL_PROVIDER in group_names }}"
  shared: "{{ MAIL_PROVIDER in group_names }}"
# nocheck: shared
# nocheck: dynamic-flag
# nocheck: playwright-service-flag: DB engine, covered by role-local integration tests
redis:
  bond: 1
  backup:
    disabled: true
  enabled: true # nocheck: dynamic-flag
  shared: "{{ 'svc-db-redis' in group_names }}"
  cpus: "1"
  mem_reservation: 256m
  mem_limit: 1g
  pids_limit: 512
  name: "{{ 'web-app-nextcloud' | get_entity_name }}-redis"
# nocheck: playwright-service-flag: DB engine, covered by role-local integration tests
mariadb:
  bond: 1
  enabled: true # nocheck: dynamic-flag
  shared: "{{ 'svc-db-mariadb' in group_names }}"
  cpus: "0.75"
  mem_reservation: 512m
  mem_limit: 1.5g
  pids_limit: 512
# nocheck: playwright-service-flag  self-provider
nextcloud:
  enabled: true
  shared: true
  bond: 1
  modes:
    compose:
      enabled: true
    swarm:
      enabled: true
  name: nextcloud
  image: nextcloud
  # nocheck: docker-version  integration_openproject v3.2.0 caps at Nextcloud 34; only its v3.3.0 nightlies declare 35, lift once 3.3.0 is stable
  version: 34-fpm-alpine
  custom: true
  min_storage: 553MB
  backup:
    no_stop_required: true
  cpus: "2.0"
  mem_reservation: 2g
  mem_limit: 3g
  pids_limit: 512
  # danger: lifts Nextcloud's SSRF guard for EVERY outbound client -- federated shares,
  # external storage, webcal, previews -- and Nextcloud offers no per-host allowlist.
  allow_local_remote_servers:
    enabled: "{{ ((ENVIRONMENT | lower) == 'development') | lower }}"
  default_quota: "1000000000"
  legacy_login_mask:
    enabled: "{{ not (lookup('config', 'web-app-nextcloud', 'services.sso.enabled') | bool) }}"
  performance:
    php:
      upload_limit: 5G
      fpm:
        max_children: 16
        start_servers: 4
        min_spare_servers: 4
        max_spare_servers: 8
        request_terminate_timeout: 900s
  plugins_enabled: true
  proxy:
    service_key: proxy
  ports:
    local:
      http: 8001
    internal:
      http: 80
      fpm: 9000
    public:
      stun_turn: 3480
      stun_turn_tls: 5350
      relay:
        start: 50000
        end: 59999
  run_after:
    - web-app-keycloak
    - web-app-matomo
    - web-app-gitlab
    - web-app-mattermost
    - web-app-openproject
    - web-app-zammad
  lifecycle: beta
proxy:
  name: nextcloud-proxy
  healthcheck:
    flavor: curl
    # nocheck: healthcheck-timing-override  a sidecar of a stack whose main service alone takes minutes; the value predates this default and has not been measured against it
    start_period: 20m
  image: nginx
  version: 1.31.8-alpine
  backup:
    no_stop_required: true
  cpus: "0.5"
  mem_reservation: 64m
  mem_limit: 256m
  pids_limit: 512
  min_storage: 226MB
  ports:
    internal:
      http: 80
# nocheck: playwright-service-flag
# nocheck: dynamic-flag  the container serves this role's MCP surface, so it tracks the role's own mcp flag rather than one client's presence
context_agent:
  bond: 1
  enabled: "{{ lookup('config', 'web-app-nextcloud', 'mcp.enabled') | bool }}"
  shared: "{{ lookup('config', 'web-app-nextcloud', 'mcp.enabled') | bool }}"
  name: nextcloud-context-agent
  image: ghcr.io/nextcloud/context_agent
  version: 2.11.0
  backup:
    no_stop_required: true
  # nocheck: single-replica
  replicas: 1
  cpus: "1.0"
  mem_reservation: 512m
  mem_limit: 1g
  pids_limit: 512
  min_storage: 512MB
  ports:
    internal:
      http: 23000
  healthcheck:
    flavor: http_status
    path: heartbeat
    # nocheck: healthcheck-timing-override  a sidecar of a stack whose main service alone takes minutes; the value predates this default and has not been measured against it
    start_period: 20m
# nocheck: playwright-service-flag  MCP sidecar, no browser surface
# nocheck: dynamic-flag  the sidecar fronts this role's MCP surface, so it tracks the role's own mcp flag rather than one client's presence
contextagentmcp:
  enabled: "{{ lookup('config', 'web-app-nextcloud', 'mcp.enabled') | bool }}"
  shared: false
  image: python
  # nocheck: docker-version  base of the self-built stdlib adapter, not the served application
  version: "3.13.15-slim"
  custom: true
  name: contextagentmcp
  cpus: "0.25"
  mem_reservation: 16m
  mem_limit: 24m
  pids_limit: 64
  healthcheck:
    flavor: http_status
    path: health
  ports:
    internal:
      http: 8080
cron:
  # nocheck: single-replica singleton cron.sh; replicas double-run background jobs on the shared data volume
  replicas: 1
  name: nextcloud-cron
  cpus: "0.25"
  mem_reservation: 128m
  mem_limit: 512m
  pids_limit: 256
  min_storage: 1GB
# nocheck: dynamic-flag
# nocheck: playwright-service-flag: infrastructural sub-component (real-time communication)
talk:
  bond: 1
  enabled: "{{ 'web-svc-coturn' in group_names }}"
  shared: "{{ 'web-svc-coturn' in group_names }}"
  name: nextcloud-talk
  image: nextcloud/aio-talk
  # nocheck: docker-version -- all 174 tags upstream publishes are AIO build stamps, none is a version
  version: latest
  backup:
    no_stop_required: false
  turn_server:
    onboard_enabled: "{{ not ( 'web-svc-coturn' in group_names ) }}"
    standalone_enabled: "{{ 'web-svc-coturn' in group_names }}"
  network_mode: bridge
  cpus: "2.0"
  mem_reservation: 1g
  mem_limit: 2g
  pids_limit: 1024
  min_storage: 274MB
  ports:
    internal:
      signaling: 8081
  healthcheck:
    flavor: tcp
    port_key: signaling
    # nocheck: healthcheck-timing-override  a sidecar of a stack whose main service alone takes minutes; the value predates this default and has not been measured against it
    start_period: 20m
whiteboard:
  name: nextcloud-whiteboard
  image: ghcr.io/nextcloud-releases/whiteboard
  version: v1.5.9
  custom: nextcloud-whiteboard
  backup:
    no_stop_required: true
  cpus: "1"
  mem_reservation: 128m
  mem_limit: 1g
  pids_limit: 1024
  min_storage: 683MB
  ports:
    internal:
      http: 3002
  healthcheck:
    flavor: nc
    # nocheck: healthcheck-timing-override  a sidecar of a stack whose main service alone takes minutes; the value predates this default and has not been measured against it
    start_period: 20m
# nocheck: playwright-service-flag: infrastructural sub-component (collaborative editing)
onlyoffice:
  bond: 0.5
  enabled: "{{ 'web-svc-onlyoffice' in group_names }}"
  shared: "{{ 'web-svc-onlyoffice' in group_names }}"
# nocheck: playwright-service-flag: infrastructural sub-component (collaborative editing)
collabora:
  bond: 1
  enabled: "{{ 'web-svc-collabora' in group_names }}"
  shared: "{{ 'web-svc-collabora' in group_names }}"
# nocheck: playwright-service-flag: bridged web-conferencing partner, covered by partner role spec
bigbluebutton:
  bond: 0.5
  enabled: "{{ 'web-app-bigbluebutton' in group_names }}"
  shared: "{{ 'web-app-bigbluebutton' in group_names }}"
# nocheck: playwright-service-flag: bridged wiki partner, covered by partner role spec
xwiki:
  bond: 0.5
  enabled: "{{ 'web-app-xwiki' in group_names }}"
  shared: "{{ 'web-app-xwiki' in group_names }}"
# nocheck: playwright-service-flag: object store, no Playwright surface
minio:
  enabled: false # nocheck: dynamic-flag
  shared: false # nocheck: dynamic-flag
seaweedfs:
  bond: 1
  enabled: "{{ 'web-app-seaweedfs' in group_names }}"
  shared: "{{ 'web-app-seaweedfs' in group_names }}"
# nocheck: playwright-service-flag  internal recording server, reachable only from the Talk signaling backend
# nocheck: dynamic-flag  the recording server is part of the Talk deployment, so it follows the talk entry rather than a role's presence
talk_recording:
  enabled: "{{ lookup('config', 'web-app-nextcloud', 'services.talk.enabled') | bool }}"
  shared: false
  name: nextcloud-talk-recording
  image: nextcloud/aio-talk-recording
  # nocheck: docker-version -- all 104 tags upstream publishes are AIO build stamps, none is a version
  version: latest
  backup:
    no_stop_required: true
  cpus: "2.0"
  mem_reservation: 1g
  mem_limit: 3g
  pids_limit: 1024
  min_storage: 507MB
  ports:
    internal:
      recording: 1234
# nocheck: playwright-service-flag: verified by web-svc-css provider spec
css:
  bond: 1
  enabled: "{{ 'web-svc-css' in group_names }}"
  shared: "{{ 'web-svc-css' in group_names }}"
# nocheck: dynamic-flag
# nocheck: playwright-service-flag: captcha backend, infrastructural
hcaptcha:
  enabled: "{{ HCAPTCHA_ENABLED | bool }}"
prometheus:
  bond: 1
  enabled: "{{ 'web-app-prometheus' in group_names }}"
  shared: "{{ 'web-app-prometheus' in group_names }}"
# nocheck: playwright-service-flag
tor:
  bond: 0.5
  enabled: "{{ 'svc-net-tor' in group_names }}"
  shared: true # nocheck: dynamic-flag
# nocheck: playwright-service-flag: bridged openproject partner, covered by partner role spec
openproject:
  bond: 0.5
  enabled: "{{ 'web-app-openproject' in group_names }}"
  shared: "{{ 'web-app-openproject' in group_names }}"
# nocheck: playwright-service-flag: bridged gitlab partner, covered by partner role spec
gitlab:
  bond: 0.5
  enabled: "{{ 'web-app-gitlab' in group_names }}"
  shared: "{{ 'web-app-gitlab' in group_names }}"
# nocheck: playwright-service-flag: bridged discourse partner, covered by partner role spec
discourse:
  bond: 0.3
  enabled: "{{ 'web-app-discourse' in group_names }}"
  shared: "{{ 'web-app-discourse' in group_names }}"
# nocheck: playwright-service-flag: bridged mattermost partner, covered by partner role spec
mattermost:
  bond: 0.5
  enabled: "{{ 'web-app-mattermost' in group_names }}"
  shared: "{{ 'web-app-mattermost' in group_names }}"
# nocheck: playwright-service-flag: bridged matrix partner, covered by partner role spec
matrix:
  bond: 0.5
  enabled: "{{ 'web-app-matrix' in group_names }}"
  shared: "{{ 'web-app-matrix' in group_names }}"
# nocheck: playwright-service-flag: bridged zammad partner, covered by partner role spec
zammad:
  bond: 0.5
  enabled: "{{ 'web-app-zammad' in group_names }}"
  shared: "{{ 'web-app-zammad' in group_names }}"
# nocheck: playwright-service-flag: bridged openwebui partner, covered by partner role spec
openwebui:
  bond: 0.5
  enabled: "{{ 'web-app-openwebui' in group_names }}"
  shared: "{{ 'web-app-openwebui' in group_names }}"
# nocheck: playwright-service-flag, dynamic-flag - bridged litellm gateway partner, covered by partner role spec, and a single shared instance
litellm:
  bond: 0.5
  enabled: "{{ LITELLM_USABLE }}"
  shared: true
# nocheck: playwright-service-flag: bridged mastodon partner, covered by partner role spec
mastodon:
  bond: 0.5
  enabled: "{{ 'web-app-mastodon' in group_names }}"
  shared: "{{ 'web-app-mastodon' in group_names }}"
# nocheck: playwright-service-flag: bridged peertube partner, covered by partner role spec
peertube:
  bond: 0.5
  enabled: "{{ 'web-app-peertube' in group_names }}"
  shared: "{{ 'web-app-peertube' in group_names }}"
# TODO(bridge): no nc-33-compatible integration_moodle on the app store (absent for nc 30/32/33).
# Re-enable enabled/shared (back to the "web-app-moodle in group_names" form) once a plugin ships.
# nocheck: playwright-service-flag: bridged moodle partner, covered by partner role spec
moodle:
  enabled: false # nocheck: dynamic-flag
  shared: false # nocheck: dynamic-flag
# TODO(bridge): integration_suitecrm requires nc <= 24 (see meta/addons/integration_suitecrm.yml).
# Re-enable enabled/shared (back to the "web-app-suitecrm in group_names" form) once a plugin ships.
# nocheck: playwright-service-flag: bridged suitecrm partner, covered by partner role spec
suitecrm:
  enabled: false # nocheck: dynamic-flag
  shared: false # nocheck: dynamic-flag
# nocheck: dynamic-flag
# nocheck: playwright-service-flag, dynamic-flag infrastructure backup consumer
container_backup: # nocheck: bond-baseline backup host joins outside the variant topology
  bond: 1
  enabled: "{{ 'svc-bkp-volume-2-local' in group_names }}"
  shared: true
# nocheck: playwright-service-flag: MCP pairing is proven by the role's own MCP spec, not by a per-consumer gate
hermes:
  bond: 0.5
  enabled: "{{ 'web-app-hermes' in group_names }}"
  shared: "{{ 'web-app-hermes' in group_names }}"
# nocheck: playwright-service-flag: MCP pairing is proven by the role's own MCP spec, not by a per-consumer gate
openclaw:
  bond: 0.5
  enabled: "{{ 'web-app-openclaw' in group_names }}"
  shared: "{{ 'web-app-openclaw' in group_names }}"
# nocheck: playwright-service-flag: MCP pairing is proven by the role's own MCP spec, not by a per-consumer gate
# nocheck: dynamic-flag  the entry refuses this client, so the link is off on every host and no group_names gate can decide it
flowise:
  enabled: false
  shared: false
  mcp_consumer: false