version: '3.8' # Appwrite - Backend-as-a-Service Platform # Purpose: Self-hosted BaaS for development and testing (auth, database, storage, functions) # Port: 8093 (console + API via Traefik), 8094 (HTTPS) # GPU: No # External: LAN only (dev/testing) # Storage: SSD (all data), internal MariaDB + Redis # Containers: ~20 (API, console, realtime, workers, schedulers, executor, MariaDB, Redis) x-logging: &x-logging logging: driver: 'json-file' options: max-file: '5' max-size: '10m' x-appwrite-env: &appwrite-env _APP_ENV: production _APP_WORKER_PER_CORE: 6 _APP_LOCALE: en _APP_DOMAIN: 192.168.86.149 _APP_DOMAIN_TARGET_CNAME: 192.168.86.149 _APP_DOMAIN_TARGET_A: 192.168.86.149 _APP_DOMAIN_FUNCTIONS: functions.localhost _APP_DOMAIN_SITES: sites.localhost _APP_CONSOLE_WHITELIST_ROOT: enabled _APP_OPENSSL_KEY_V1: 2b8fd82b91b25205332c9d219a5838a752125965c6ea8ecfb0afe02e3307e163 _APP_EXECUTOR_SECRET: gbnq4JwfnXBm4J7h7kUNEWt+OhwV3agu _APP_EXECUTOR_HOST: http://openruntimes-executor/v1 _APP_DB_ADAPTER: mariadb _APP_DB_HOST: appwrite-mariadb _APP_DB_PORT: '3306' _APP_DB_SCHEMA: appwrite _APP_DB_USER: appwrite _APP_DB_PASS: ML8QbYgD05+7ZDHuRfmSTgDBr5VusxLi87ifyO5RlJg= _APP_DB_ROOT_PASS: 2b8fd82b91b25205332c9d219a5838a752125965c6ea8ecfb0afe02e3307e163 _APP_REDIS_HOST: appwrite-redis _APP_REDIS_PORT: '6379' _APP_REDIS_USER: '' _APP_REDIS_PASS: '' _APP_STORAGE_LIMIT: '30000000' _APP_STORAGE_PREVIEW_LIMIT: '20000000' _APP_STORAGE_ANTIVIRUS: disabled _APP_STORAGE_DEVICE: local _APP_USAGE_STATS: enabled _APP_OPTIONS_ABUSE: enabled _APP_OPTIONS_FORCE_HTTPS: disabled _APP_OPTIONS_ROUTER_PROTECTION: disabled _APP_OPTIONS_ROUTER_FORCE_HTTPS: disabled _APP_SYSTEM_EMAIL_NAME: Appwrite _APP_SYSTEM_EMAIL_ADDRESS: appwrite@schweitz.net _APP_EMAIL_SECURITY: '' _APP_SYSTEM_RESPONSE_FORMAT: '' _APP_GRAPHQL_INTROSPECTION: disabled _APP_GRAPHQL_MAX_BATCH_SIZE: '10' _APP_GRAPHQL_MAX_COMPLEXITY: '250' _APP_GRAPHQL_MAX_DEPTH: '3' _APP_MAINTENANCE_INTERVAL: '86400' _APP_MAINTENANCE_RETENTION_EXECUTION: '1209600' _APP_MAINTENANCE_RETENTION_CACHE: '2592000' _APP_MAINTENANCE_RETENTION_ABUSE: '86400' _APP_MAINTENANCE_RETENTION_AUDIT: '1209600' _APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE: '1209600' _APP_MAINTENANCE_RETENTION_USAGE_HOURLY: '8640000' _APP_MAINTENANCE_RETENTION_SCHEDULES: '86400' _APP_FUNCTIONS_TIMEOUT: '900' _APP_SITES_TIMEOUT: '900' _APP_COMPUTE_BUILD_TIMEOUT: '900' _APP_COMPUTE_CPUS: '0' _APP_COMPUTE_MEMORY: '0' _APP_COMPUTE_SIZE_LIMIT: '30000000' _APP_FUNCTIONS_RUNTIMES: node-22.0,python-3.12,php-8.3,dart-3.5,bun-1.1 _APP_SITES_RUNTIMES: static-1.0 _APP_COMPUTE_RUNTIMES_NETWORK: runtimes _APP_COMPUTE_INACTIVE_THRESHOLD: '60' _APP_COMPUTE_MAINTENANCE_INTERVAL: '60' _APP_LOGGING_CONFIG: '' _APP_DNS: '' _APP_COMPRESSION_MIN_SIZE_BYTES: '1024' TZ: Europe/Amsterdam services: # ============================================ # Traefik - Internal Reverse Proxy # Port: 8093 (HTTP), 8094 (HTTPS) # Routes /console to console, /v1/realtime to realtime, everything else to API # ============================================ appwrite-traefik: image: traefik:3.6 container_name: appwrite-traefik <<: *x-logging restart: unless-stopped command: - --providers.file.directory=/storage/config - --providers.file.watch=true - --providers.docker=true - --providers.docker.exposedByDefault=false - --providers.docker.constraints=Label(`traefik.constraint-label-stack`,`appwrite`) - --entrypoints.appwrite_web.address=:80 - --entrypoints.appwrite_websecure.address=:443 ports: - "8093:80" - "8094:443" volumes: - /var/run/docker.sock:/var/run/docker.sock - /home/jpmschweitzer/docker-data/appwrite/config:/storage/config:ro - /home/jpmschweitzer/docker-data/appwrite/certificates:/storage/certificates:ro depends_on: - appwrite networks: - appwrite - docker-dataplane # ============================================ # Appwrite API Server # Internal only - served via Traefik # ============================================ appwrite: image: appwrite/appwrite:1.9.0 container_name: appwrite <<: *x-logging restart: unless-stopped networks: - appwrite labels: - traefik.enable=true - traefik.constraint-label-stack=appwrite - traefik.docker.network=appwrite - traefik.http.services.appwrite_api.loadbalancer.server.port=80 - traefik.http.routers.appwrite_api_http.entrypoints=appwrite_web - traefik.http.routers.appwrite_api_http.rule=PathPrefix(`/`) - traefik.http.routers.appwrite_api_http.service=appwrite_api - traefik.http.routers.appwrite_api_https.entrypoints=appwrite_websecure - traefik.http.routers.appwrite_api_https.rule=PathPrefix(`/`) - traefik.http.routers.appwrite_api_https.service=appwrite_api - traefik.http.routers.appwrite_api_https.tls=true - "com.centurylinklabs.watchtower.enable=false" volumes: - /home/jpmschweitzer/docker-data/appwrite/uploads:/storage/uploads:rw - /home/jpmschweitzer/docker-data/appwrite/imports:/storage/imports:rw - /home/jpmschweitzer/docker-data/appwrite/cache:/storage/cache:rw - /home/jpmschweitzer/docker-data/appwrite/config:/storage/config:rw - /home/jpmschweitzer/docker-data/appwrite/certificates:/storage/certificates:rw - /home/jpmschweitzer/docker-data/appwrite/functions:/storage/functions:rw - /home/jpmschweitzer/docker-data/appwrite/sites:/storage/sites:rw - /home/jpmschweitzer/docker-data/appwrite/builds:/storage/builds:rw depends_on: appwrite-redis: condition: service_healthy appwrite-mariadb: condition: service_healthy environment: <<: *appwrite-env # ============================================ # Appwrite Console - Web UI # Internal only - served via Traefik at /console # ============================================ appwrite-console: image: appwrite/console:7.8.26 container_name: appwrite-console <<: *x-logging restart: unless-stopped networks: - appwrite labels: - traefik.enable=true - traefik.constraint-label-stack=appwrite - traefik.docker.network=appwrite - traefik.http.services.appwrite_console.loadbalancer.server.port=80 - traefik.http.routers.appwrite_console_http.entrypoints=appwrite_web - traefik.http.routers.appwrite_console_http.rule=PathPrefix(`/console`) - traefik.http.routers.appwrite_console_http.service=appwrite_console - traefik.http.routers.appwrite_console_https.entrypoints=appwrite_websecure - traefik.http.routers.appwrite_console_https.rule=PathPrefix(`/console`) - traefik.http.routers.appwrite_console_https.service=appwrite_console - traefik.http.routers.appwrite_console_https.tls=true - "com.centurylinklabs.watchtower.enable=false" # ============================================ # Appwrite Realtime - WebSocket Server # Internal only - served via Traefik at /v1/realtime # ============================================ appwrite-realtime: image: appwrite/appwrite:1.9.0 entrypoint: realtime container_name: appwrite-realtime <<: *x-logging restart: unless-stopped networks: - appwrite labels: - traefik.enable=true - traefik.constraint-label-stack=appwrite - traefik.docker.network=appwrite - traefik.http.services.appwrite_realtime.loadbalancer.server.port=80 - traefik.http.routers.appwrite_realtime_ws.entrypoints=appwrite_web - traefik.http.routers.appwrite_realtime_ws.rule=PathPrefix(`/v1/realtime`) - traefik.http.routers.appwrite_realtime_ws.service=appwrite_realtime - traefik.http.routers.appwrite_realtime_wss.entrypoints=appwrite_websecure - traefik.http.routers.appwrite_realtime_wss.rule=PathPrefix(`/v1/realtime`) - traefik.http.routers.appwrite_realtime_wss.service=appwrite_realtime - traefik.http.routers.appwrite_realtime_wss.tls=true depends_on: appwrite-redis: condition: service_healthy appwrite-mariadb: condition: service_healthy environment: <<: *appwrite-env # ============================================ # Workers - Background Processing # ============================================ appwrite-worker-audits: image: appwrite/appwrite:1.9.0 entrypoint: worker-audits container_name: appwrite-worker-audits <<: *x-logging restart: unless-stopped networks: - appwrite depends_on: appwrite-redis: condition: service_healthy appwrite-mariadb: condition: service_healthy environment: <<: *appwrite-env appwrite-worker-webhooks: image: appwrite/appwrite:1.9.0 entrypoint: worker-webhooks container_name: appwrite-worker-webhooks <<: *x-logging restart: unless-stopped networks: - appwrite depends_on: appwrite-redis: condition: service_healthy appwrite-mariadb: condition: service_healthy environment: <<: *appwrite-env appwrite-worker-deletes: image: appwrite/appwrite:1.9.0 entrypoint: worker-deletes container_name: appwrite-worker-deletes <<: *x-logging restart: unless-stopped networks: - appwrite volumes: - /home/jpmschweitzer/docker-data/appwrite/uploads:/storage/uploads:rw - /home/jpmschweitzer/docker-data/appwrite/cache:/storage/cache:rw - /home/jpmschweitzer/docker-data/appwrite/functions:/storage/functions:rw - /home/jpmschweitzer/docker-data/appwrite/sites:/storage/sites:rw - /home/jpmschweitzer/docker-data/appwrite/builds:/storage/builds:rw - /home/jpmschweitzer/docker-data/appwrite/certificates:/storage/certificates:rw depends_on: appwrite-redis: condition: service_healthy appwrite-mariadb: condition: service_healthy environment: <<: *appwrite-env appwrite-worker-databases: image: appwrite/appwrite:1.9.0 entrypoint: worker-databases container_name: appwrite-worker-databases <<: *x-logging restart: unless-stopped networks: - appwrite depends_on: appwrite-redis: condition: service_healthy appwrite-mariadb: condition: service_healthy environment: <<: *appwrite-env appwrite-worker-builds: image: appwrite/appwrite:1.9.0 entrypoint: worker-builds container_name: appwrite-worker-builds <<: *x-logging restart: unless-stopped networks: - appwrite volumes: - /home/jpmschweitzer/docker-data/appwrite/functions:/storage/functions:rw - /home/jpmschweitzer/docker-data/appwrite/sites:/storage/sites:rw - /home/jpmschweitzer/docker-data/appwrite/builds:/storage/builds:rw - /home/jpmschweitzer/docker-data/appwrite/uploads:/storage/uploads:rw depends_on: appwrite-redis: condition: service_healthy appwrite-mariadb: condition: service_healthy environment: <<: *appwrite-env appwrite-worker-certificates: image: appwrite/appwrite:1.9.0 entrypoint: worker-certificates container_name: appwrite-worker-certificates <<: *x-logging restart: unless-stopped networks: - appwrite volumes: - /home/jpmschweitzer/docker-data/appwrite/config:/storage/config:rw - /home/jpmschweitzer/docker-data/appwrite/certificates:/storage/certificates:rw depends_on: appwrite-redis: condition: service_healthy appwrite-mariadb: condition: service_healthy environment: <<: *appwrite-env appwrite-worker-functions: image: appwrite/appwrite:1.9.0 entrypoint: worker-functions container_name: appwrite-worker-functions <<: *x-logging restart: unless-stopped networks: - appwrite depends_on: appwrite-redis: condition: service_healthy appwrite-mariadb: condition: service_healthy openruntimes-executor: condition: service_started environment: <<: *appwrite-env appwrite-worker-mails: image: appwrite/appwrite:1.9.0 entrypoint: worker-mails container_name: appwrite-worker-mails <<: *x-logging restart: unless-stopped networks: - appwrite depends_on: appwrite-redis: condition: service_healthy appwrite-mariadb: condition: service_healthy environment: <<: *appwrite-env appwrite-worker-messaging: image: appwrite/appwrite:1.9.0 entrypoint: worker-messaging container_name: appwrite-worker-messaging <<: *x-logging restart: unless-stopped networks: - appwrite volumes: - /home/jpmschweitzer/docker-data/appwrite/uploads:/storage/uploads:rw depends_on: appwrite-redis: condition: service_healthy appwrite-mariadb: condition: service_healthy environment: <<: *appwrite-env appwrite-worker-migrations: image: appwrite/appwrite:1.9.0 entrypoint: worker-migrations container_name: appwrite-worker-migrations <<: *x-logging restart: unless-stopped networks: - appwrite volumes: - /home/jpmschweitzer/docker-data/appwrite/imports:/storage/imports:rw depends_on: appwrite-redis: condition: service_healthy appwrite-mariadb: condition: service_healthy environment: <<: *appwrite-env # ============================================ # Stats Workers # ============================================ appwrite-task-stats-resources: image: appwrite/appwrite:1.9.0 entrypoint: stats-resources container_name: appwrite-task-stats-resources <<: *x-logging restart: unless-stopped networks: - appwrite depends_on: appwrite-redis: condition: service_healthy appwrite-mariadb: condition: service_healthy environment: <<: *appwrite-env appwrite-worker-stats-resources: image: appwrite/appwrite:1.9.0 entrypoint: worker-stats-resources container_name: appwrite-worker-stats-resources <<: *x-logging restart: unless-stopped networks: - appwrite depends_on: appwrite-redis: condition: service_healthy appwrite-mariadb: condition: service_healthy environment: <<: *appwrite-env appwrite-worker-stats-usage: image: appwrite/appwrite:1.9.0 entrypoint: worker-stats-usage container_name: appwrite-worker-stats-usage <<: *x-logging restart: unless-stopped networks: - appwrite depends_on: appwrite-redis: condition: service_healthy appwrite-mariadb: condition: service_healthy environment: <<: *appwrite-env # ============================================ # Task Schedulers # ============================================ appwrite-task-scheduler-functions: image: appwrite/appwrite:1.9.0 entrypoint: schedule-functions container_name: appwrite-task-scheduler-functions <<: *x-logging restart: unless-stopped networks: - appwrite depends_on: appwrite-redis: condition: service_healthy appwrite-mariadb: condition: service_healthy environment: <<: *appwrite-env appwrite-task-scheduler-executions: image: appwrite/appwrite:1.9.0 entrypoint: schedule-executions container_name: appwrite-task-scheduler-executions <<: *x-logging restart: unless-stopped networks: - appwrite depends_on: appwrite-redis: condition: service_healthy appwrite-mariadb: condition: service_healthy environment: <<: *appwrite-env appwrite-task-scheduler-messages: image: appwrite/appwrite:1.9.0 entrypoint: schedule-messages container_name: appwrite-task-scheduler-messages <<: *x-logging restart: unless-stopped networks: - appwrite depends_on: appwrite-redis: condition: service_healthy appwrite-mariadb: condition: service_healthy environment: <<: *appwrite-env # ============================================ # Maintenance Task # ============================================ appwrite-task-maintenance: image: appwrite/appwrite:1.9.0 entrypoint: maintenance container_name: appwrite-task-maintenance <<: *x-logging restart: unless-stopped networks: - appwrite depends_on: appwrite-redis: condition: service_healthy appwrite-mariadb: condition: service_healthy environment: <<: *appwrite-env # ============================================ # Browser Service (SSR rendering) # ============================================ appwrite-browser: image: appwrite/browser:0.3.2 container_name: appwrite-browser <<: *x-logging restart: unless-stopped networks: - appwrite # ============================================ # OpenRuntimes - Serverless Function Executor # Requires Docker socket for spawning runtime containers # ============================================ openruntimes-executor: image: openruntimes/executor:0.7.22 container_name: openruntimes-executor hostname: exc1 <<: *x-logging restart: unless-stopped stop_signal: SIGINT networks: - appwrite - runtimes volumes: - /var/run/docker.sock:/var/run/docker.sock - /home/jpmschweitzer/docker-data/appwrite/builds:/storage/builds:rw - /home/jpmschweitzer/docker-data/appwrite/functions:/storage/functions:rw - /home/jpmschweitzer/docker-data/appwrite/sites:/storage/sites:rw - /tmp:/tmp:rw environment: - OPR_EXECUTOR_INACTIVE_TRESHOLD=${_APP_COMPUTE_INACTIVE_THRESHOLD:-60} - OPR_EXECUTOR_MAINTENANCE_INTERVAL=${_APP_COMPUTE_MAINTENANCE_INTERVAL:-60} - OPR_EXECUTOR_NETWORK=runtimes - OPR_EXECUTOR_ENV=production - OPR_EXECUTOR_RUNTIMES=node-22.0,python-3.12,php-8.3,dart-3.5,bun-1.1,static-1.0 - OPR_EXECUTOR_SECRET=gbnq4JwfnXBm4J7h7kUNEWt+OhwV3agu - OPR_EXECUTOR_RUNTIME_VERSIONS=v5 - OPR_EXECUTOR_LOGGING_CONFIG= - OPR_EXECUTOR_STORAGE_DEVICE=local # ============================================ # MariaDB - Internal Database (Appwrite core component) # NOT exposed to host - Appwrite-only # ============================================ appwrite-mariadb: image: mariadb:10.11 container_name: appwrite-mariadb <<: *x-logging restart: unless-stopped networks: - appwrite volumes: - /home/jpmschweitzer/docker-data/appwrite/mariadb:/var/lib/mysql:rw environment: MYSQL_ROOT_PASSWORD: 2b8fd82b91b25205332c9d219a5838a752125965c6ea8ecfb0afe02e3307e163 MYSQL_DATABASE: appwrite MYSQL_USER: appwrite MYSQL_PASSWORD: ML8QbYgD05+7ZDHuRfmSTgDBr5VusxLi87ifyO5RlJg= MARIADB_AUTO_UPGRADE: '1' TZ: Europe/Amsterdam command: 'mysqld --innodb-flush-method=fsync' healthcheck: test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] interval: 10s timeout: 10s retries: 10 start_period: 30s # ============================================ # Redis - Internal Cache/Queue (Appwrite core component) # NOT exposed to host - lives and dies with the stack # ============================================ appwrite-redis: image: redis:7.4-alpine container_name: appwrite-redis <<: *x-logging restart: unless-stopped networks: - appwrite command: > redis-server --maxmemory 512mb --maxmemory-policy allkeys-lru --maxmemory-samples 5 volumes: - /home/jpmschweitzer/docker-data/appwrite/redis:/data:rw healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 5s retries: 5 start_period: 10s networks: appwrite: name: appwrite runtimes: name: runtimes docker-dataplane: external: true name: docker-dataplane # ============================================================================= # DEPLOYMENT INSTRUCTIONS # ============================================================================= # # Phase 1: Data Directories (DONE) # Created at ~/docker-data/appwrite/{uploads,imports,cache,config,certificates, # functions,sites,builds,mariadb,redis} # # Phase 2: Deploy Stack # Deploy via Portainer as stack "appwrite" # All environment variables are inline - no .env file needed # # Phase 3: Initial Setup # 1. Access http://192.168.86.149:8093/console # 2. Create admin account (first user becomes admin) # 3. Create a project for development # # Phase 4: (Optional) NPM Proxy Configuration # If external access is needed later: # 1. Create proxy host: appwrite.schweitz.net -> 192.168.86.149:8093 # 2. Enable: Block Common Exploits, Websockets Support # 3. SSL: Let's Encrypt, Force SSL, HTTP/2 # # ============================================================================= # CREDENTIALS # ============================================================================= # MariaDB: # Host: appwrite-mariadb:3306 (internal only) # Database: appwrite # User: appwrite # Password: ML8QbYgD05+7ZDHuRfmSTgDBr5VusxLi87ifyO5RlJg= # Root Password: 2b8fd82b91b25205332c9d219a5838a752125965c6ea8ecfb0afe02e3307e163 # # Redis: # Host: appwrite-redis:6379 (internal only, no password) # # OpenSSL Key: 2b8fd82b91b25205332c9d219a5838a752125965c6ea8ecfb0afe02e3307e163 # Executor Secret: gbnq4JwfnXBm4J7h7kUNEWt+OhwV3agu # # ============================================================================= # ARCHITECTURE NOTES # ============================================================================= # - MariaDB and Redis are internal to the stack (not exposed to host) # - Traefik handles routing: /console -> console, /v1/realtime -> realtime, / -> API # - OpenRuntimes executor needs Docker socket to spawn function containers # - The 'runtimes' network is used for executor <-> function container communication # - Watchtower is disabled (label) - Appwrite upgrades should be deliberate # - All 20+ containers share the same appwrite/appwrite:1.9.0 image with different entrypoints