Skip to content
On this page

Decentralization

Decentralization is the first step toward horizontal scaling. It also can be a vital part for a single-server infrastructure.

Good to know

All the docker-compose.yml and .env files are available in the book repo.

What is decentralization?

Decentralization is the opposite of centralization.

Meaning, instead of centering and gathering all the services into a single place, you are placing one or more service in one location, while placing other services in other location(s).

This process called decentralization.

In order for us to be able to apply the decentralization principles in Appwrite, you need first to group Appwrite infrastructure components into 3 groups.

  1. Storage - Docker volumes. These are the virtual drives being used by Appwrite containers. One volume can be in use by more than one container.
  2. Application - Databases. These components need to have single endpoint - usually - which makes them very hard to scale.
  3. Services - Endpoints & Workers. These components are logical components. Meaning they don't have any changeable data inside them, which makes them very easy to scale and replicate.

In the process, you will separate those servers in the at-least two servers, placing the storage and applications into one non-replicable server. And, the services into one replicable server.

In this chart, you can see a possible deploying of a decentralized Appwrite infrastructure when all the replicable services use one, and only one source of truth.

null

The dashed line represents connection to a storage volumes while the solid one represents connection to an application

Storage

You're going to use the SSHFS - File system over SSH - protocol. This will allow you to share folders from one server to all other servers in the local network.

Ubuntu Style

Is worth noting that in this chapter and the following ones You're going to install all the needed software using Ubuntu 22.04 server.

Usually it means you can run it in any Debian based Linux distros.

When dealing with sshfs you have at least two servers.

  • Main — A server that hosts the folders you want to be available over the network.
  • Client - one or more servers in the local network that have the Main server folder mounted as a local directory.

Configure the Main server

In the Main server, you've only 2 steps to take:

  1. Create the folders.
  2. Create and save a public SSH key.

Create a directory in the home folder and name it share. Then cd into it and create a folder for each of Appwrite volumes

shell
mkdir /home/share
cd /home/share
mkdir mariadb redis cache uploads certificates functions builds config executor

Creating SSH Keys

You'll now create the SSH key. The private key will be used by the Client servers, while the public one will be added to the Main authorized_keys file.

This process will let the Client servers to connect to the Main server without having to enter a password.

First, generate the SSH key, give it any name you want - sshfs - in this case and an empty passphrase.

shell
> ssh-keygen -t ed25519
Enter file in which to save the key (/root/.ssh/id_ed25519): sshfs 
Enter passphrase (empty for no passphrase): 
Enter same passphrase again:

Now append the sshfs.pub content to the authorized_keys by running:

shell
cat sshfs.pub >> /root/.ssh/authorized_keys

Mounting the sshfs folders

Copy the sshfs (the private key) to the client server /root/.ssh folder and name it id_rsa. Then, set the file permission to 0600 by running:

shell
chmod 600 /root/.ssh/id_rsa

Then, install the sshfs library, and, create the share directory.

shell
apt update
apt install sshfs

mkdir /home/share

For this step, you'll need to know your Main server internal IP address, I'll use 10.116.0.2 as my Main server IP.

By running this line you'll add a mounting command to the linux system so it will mount all the share folder at boot.

shell
echo '[email protected]:/home/share /home/share fuse.sshfs allow_other,x-systemd.automount' >> /etc/fstab

Now you can run

shell
mount -a

Running this command will try to mount all the values inside the /etc/fstab file. After that you can run this command to verify you can see all share subdirectories.

shell
ls /home/share

Good to know

  1. It's best to reboot the server, and ls the /home/share directory once more to verify that everything in order.
  2. You won't need to it over and over, as when you learn about horizontal scaling, you will create a snapshot of the already-configured client server and reuse it!

Security

When using sshfs you must take extra steps for securing the data transfer. You'll cover all of it in the Security chapter.

Databases

This tutorial ain't going to cover Database scaling or manual setup of Database cluster rather you're going to use a single node cluster for deploying them.

Managed DBs

It worth checking managed services for these 3 services in case you need to have HA database.

  • Redis — Many IaaS providers offer a managed Redis as DigitalOcean and Upstash
  • MariaDB - There aren't too many manageable out-of-the-box services for manage MariaDB, one that pops out - a bit pricey - is MariaDB SkySQL

In the future, Appwrite will support PostgreSQL and MySQL without any modifications. Those databases have plenty of managed services out there for any scale.

You can use the storage server for hosting your databases. Just make sure you've Docker installed on the server. For more details, you can follow the beginning of this chapter.

Installation process

  1. Create a folder inside the root folder name it databases.
  2. Create a docker-compose.yml file that contains only the database information
  3. Create a .env file with all the database value.
  4. Start the Docker compose.
bash
mkdir /root/databases
cd /root/databases
yaml
version: '3'

services:
   mariadb:
      image: mariadb:10.11
      container_name: appwrite-mariadb
      ports:
         - "3306:3306"
      restart: unless-stopped
      networks:
         - gateway
      volumes:
         - appwrite-mariadb:/var/lib/mysql:rw
      environment:
         - MYSQL_ROOT_PASSWORD
         - MYSQL_DATABASE
         - MYSQL_USER
         - MYSQL_PASSWORD
      command: 'mysqld --innodb-flush-method=fsync'

   redis:
      image: redis:7.2.4-alpine
      container_name: appwrite-redis
      restart: unless-stopped
      ports:
         - "6379:6379"
      command: >
         redis-server
         --maxmemory            512mb
         --maxmemory-policy     allkeys-lru
         --maxmemory-samples    5
      networks:
         - gateway
      volumes:
         - appwrite-redis:/data:rw

networks:
   gateway:

volumes:
   appwrite-mariadb:
      driver: local
      driver_opts:
         type: volume
         o: "bind"
         device: "/home/share/mariadb"
   appwrite-redis:
      driver: local
      driver_opts:
         type: volume
         o: "bind"
         device: "/home/share/redis"
ini
MYSQL_ROOT_PASSWORD = change_root_passwrod
MYSQL_DATABASE = appwrite
MYSQL_USER = appwrite
MYSQL_PASSWORD = change_user_password
bash
docker compose up -d

Make sure to change the passwords for the MariaDB database.

In that volumes part of the docker-compose.yml you are using the binding option. This setting will tell docker in which local folder the volume is. Using this method will let you use any folder, even an sshfs one as a volume for your docker services.

Services

Now that you have set the storage and databases, it's time to set the rest of the services, which can logically divide into 3 groups:

  1. Endpoints - Services that responding to outside requests from the internet through traefik.
  2. Standalone - Services that responding to an internal requests.
  3. Workers — Services that are triggered by pre-defined cron schedule.

You also have Traefik which is the revers-proxy being used by Appwrite to handle incoming requests.

Because you've extracted - decentralized - your storage and databases, the rest of the services will be very easy to scale.

Meaning, for example, you can easily replicate the appwrite container, then, even that you have many containers that answer the same requests, they all getting their data from a single source of truth, the databases and the storage.

Customizing the Docker file

You'll need to use a customized version of Appwrite docker-compose.yml, one which is stripped out from the Databases services declaration and dependencies and change the bind volume syntax.

When you're using the decentralized approach you have a disadvantage that you can't upgrade Appwrite automatically as this will change your customized docker-compose.yml file. But, fear not as manual upgrade is very easy.

Here's the customized docker-compose.yml file.
yaml
x-logging: &x-logging
  logging:
    driver: 'json-file'
    options:
      max-file: '5'
      max-size: '10m'
version: '3'

services:
  traefik:
    image: traefik:2.7
    container_name: appwrite-traefik
    <<: *x-logging
    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
    restart: unless-stopped
    ports:
      - 80:80
      - 443:443
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - appwrite-config:/storage/config:ro
      - appwrite-certificates:/storage/certificates:ro
    depends_on:
      - appwrite
    networks:
      - gateway
      - appwrite

  appwrite:
    image: appwrite/appwrite:1.3.8
    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.appwrite-only.appwrite_api.loadbalancer.server.port=80
      #http
      - 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
      # https
      - 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
    volumes:
      - appwrite-uploads:/storage/uploads:rw
      - appwrite-cache:/storage/cache:rw
      - appwrite-config:/storage/config:rw
      - appwrite-certificates:/storage/certificates:rw
      - appwrite-functions:/storage/functions:rw
    environment:
      - _APP_ENV
      - _APP_WORKER_PER_CORE
      - _APP_LOCALE
      - _APP_CONSOLE_WHITELIST_ROOT
      - _APP_CONSOLE_WHITELIST_EMAILS
      - _APP_CONSOLE_WHITELIST_IPS
      - _APP_SYSTEM_EMAIL_NAME
      - _APP_SYSTEM_EMAIL_ADDRESS
      - _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
      - _APP_SYSTEM_RESPONSE_FORMAT
      - _APP_OPTIONS_ABUSE
      - _APP_OPTIONS_FORCE_HTTPS
      - _APP_OPENSSL_KEY_V1
      - _APP_DOMAIN
      - _APP_DOMAIN_TARGET
      - _APP_REDIS_HOST
      - _APP_REDIS_PORT
      - _APP_REDIS_USER
      - _APP_REDIS_PASS
      - _APP_DB_HOST
      - _APP_DB_PORT
      - _APP_DB_SCHEMA
      - _APP_DB_USER
      - _APP_DB_PASS
      - _APP_SMTP_HOST
      - _APP_SMTP_PORT
      - _APP_SMTP_SECURE
      - _APP_SMTP_USERNAME
      - _APP_SMTP_PASSWORD
      - _APP_USAGE_STATS
      - _APP_STORAGE_LIMIT
      - _APP_STORAGE_PREVIEW_LIMIT
      - _APP_STORAGE_ANTIVIRUS
      - _APP_STORAGE_ANTIVIRUS_HOST
      - _APP_STORAGE_ANTIVIRUS_PORT
      - _APP_STORAGE_DEVICE
      - _APP_STORAGE_S3_ACCESS_KEY
      - _APP_STORAGE_S3_SECRET
      - _APP_STORAGE_S3_REGION
      - _APP_STORAGE_S3_BUCKET
      - _APP_STORAGE_DO_SPACES_ACCESS_KEY
      - _APP_STORAGE_DO_SPACES_SECRET
      - _APP_STORAGE_DO_SPACES_REGION
      - _APP_STORAGE_DO_SPACES_BUCKET
      - _APP_STORAGE_BACKBLAZE_ACCESS_KEY
      - _APP_STORAGE_BACKBLAZE_SECRET
      - _APP_STORAGE_BACKBLAZE_REGION
      - _APP_STORAGE_BACKBLAZE_BUCKET
      - _APP_STORAGE_LINODE_ACCESS_KEY
      - _APP_STORAGE_LINODE_SECRET
      - _APP_STORAGE_LINODE_REGION
      - _APP_STORAGE_LINODE_BUCKET
      - _APP_STORAGE_WASABI_ACCESS_KEY
      - _APP_STORAGE_WASABI_SECRET
      - _APP_STORAGE_WASABI_REGION
      - _APP_STORAGE_WASABI_BUCKET
      - _APP_FUNCTIONS_SIZE_LIMIT
      - _APP_FUNCTIONS_TIMEOUT
      - _APP_FUNCTIONS_BUILD_TIMEOUT
      - _APP_FUNCTIONS_CONTAINERS
      - _APP_FUNCTIONS_CPUS
      - _APP_FUNCTIONS_MEMORY
      - _APP_FUNCTIONS_MEMORY_SWAP
      - _APP_FUNCTIONS_RUNTIMES
      - _APP_EXECUTOR_SECRET
      - _APP_EXECUTOR_HOST
      - _APP_LOGGING_PROVIDER
      - _APP_LOGGING_CONFIG
      - _APP_STATSD_HOST
      - _APP_STATSD_PORT
      - _APP_MAINTENANCE_INTERVAL
      - _APP_MAINTENANCE_RETENTION_EXECUTION
      - _APP_MAINTENANCE_RETENTION_CACHE
      - _APP_MAINTENANCE_RETENTION_ABUSE
      - _APP_MAINTENANCE_RETENTION_AUDIT
      - _APP_MAINTENANCE_RETENTION_USAGE_HOURLY
      - _APP_SMS_PROVIDER
      - _APP_SMS_FROM
      - _APP_GRAPHQL_MAX_BATCH_SIZE
      - _APP_GRAPHQL_MAX_COMPLEXITY
      - _APP_GRAPHQL_MAX_DEPTH

  appwrite-realtime:
    image: appwrite/appwrite:1.3.8
    entrypoint: realtime
    container_name: appwrite-realtime
    <<: *x-logging
    restart: unless-stopped
    labels:
      - "traefik.enable=true"
      - "traefik.constraint-label-stack=appwrite"
      - "traefik.docker.network=appwrite"
      - "traefik.http.appwrite-only.appwrite_realtime.loadbalancer.server.port=80"
      #ws
      - 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
      # wss
      - 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
      - traefik.http.routers.appwrite_realtime_wss.tls.certresolver=dns
    networks:
      - appwrite
    environment:
      - _APP_ENV
      - _APP_WORKER_PER_CORE
      - _APP_OPTIONS_ABUSE
      - _APP_OPENSSL_KEY_V1
      - _APP_REDIS_HOST
      - _APP_REDIS_PORT
      - _APP_DB_HOST
      - _APP_DB_PORT
      - _APP_DB_SCHEMA
      - _APP_DB_USER
      - _APP_DB_PASS
      - _APP_USAGE_STATS
      - _APP_LOGGING_PROVIDER
      - _APP_LOGGING_CONFIG

  appwrite-worker-audits:
    image: appwrite/appwrite:1.3.8
    entrypoint: worker-audits
    <<: *x-logging
    container_name: appwrite-worker-audits
    restart: unless-stopped
    networks:
      - appwrite
    environment:
      - _APP_ENV
      - _APP_OPENSSL_KEY_V1
      - _APP_REDIS_HOST
      - _APP_REDIS_PORT
      - _APP_REDIS_USER
      - _APP_REDIS_PASS
      - _APP_DB_HOST
      - _APP_DB_PORT
      - _APP_DB_SCHEMA
      - _APP_DB_USER
      - _APP_DB_PASS
      - _APP_LOGGING_PROVIDER
      - _APP_LOGGING_CONFIG

  appwrite-worker-webhooks:
    image: appwrite/appwrite:1.3.8
    entrypoint: worker-webhooks
    <<: *x-logging
    container_name: appwrite-worker-webhooks
    restart: unless-stopped
    networks:
      - appwrite
    environment:
      - _APP_ENV
      - _APP_OPENSSL_KEY_V1
      - _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
      - _APP_REDIS_HOST
      - _APP_REDIS_PORT
      - _APP_REDIS_USER
      - _APP_REDIS_PASS
      - _APP_LOGGING_PROVIDER
      - _APP_LOGGING_CONFIG

  appwrite-worker-deletes:
    image: appwrite/appwrite:1.3.8
    entrypoint: worker-deletes
    <<: *x-logging
    container_name: appwrite-worker-deletes
    restart: unless-stopped
    networks:
      - appwrite
    volumes:
      - appwrite-uploads:/storage/uploads:rw
      - appwrite-cache:/storage/cache:rw
      - appwrite-functions:/storage/functions:rw
      - appwrite-builds:/storage/builds:rw
      - appwrite-certificates:/storage/certificates:rw
    environment:
      - _APP_ENV
      - _APP_OPENSSL_KEY_V1
      - _APP_REDIS_HOST
      - _APP_REDIS_PORT
      - _APP_REDIS_USER
      - _APP_REDIS_PASS
      - _APP_DB_HOST
      - _APP_DB_PORT
      - _APP_DB_SCHEMA
      - _APP_DB_USER
      - _APP_DB_PASS
      - _APP_STORAGE_DEVICE
      - _APP_STORAGE_S3_ACCESS_KEY
      - _APP_STORAGE_S3_SECRET
      - _APP_STORAGE_S3_REGION
      - _APP_STORAGE_S3_BUCKET
      - _APP_STORAGE_DO_SPACES_ACCESS_KEY
      - _APP_STORAGE_DO_SPACES_SECRET
      - _APP_STORAGE_DO_SPACES_REGION
      - _APP_STORAGE_DO_SPACES_BUCKET
      - _APP_STORAGE_BACKBLAZE_ACCESS_KEY
      - _APP_STORAGE_BACKBLAZE_SECRET
      - _APP_STORAGE_BACKBLAZE_REGION
      - _APP_STORAGE_BACKBLAZE_BUCKET
      - _APP_STORAGE_LINODE_ACCESS_KEY
      - _APP_STORAGE_LINODE_SECRET
      - _APP_STORAGE_LINODE_REGION
      - _APP_STORAGE_LINODE_BUCKET
      - _APP_STORAGE_WASABI_ACCESS_KEY
      - _APP_STORAGE_WASABI_SECRET
      - _APP_STORAGE_WASABI_REGION
      - _APP_STORAGE_WASABI_BUCKET
      - _APP_LOGGING_PROVIDER
      - _APP_LOGGING_CONFIG
      - _APP_EXECUTOR_SECRET
      - _APP_EXECUTOR_HOST

  appwrite-worker-databases:
    image: appwrite/appwrite:1.3.8
    entrypoint: worker-databases
    <<: *x-logging
    container_name: appwrite-worker-databases
    restart: unless-stopped
    networks:
      - appwrite
    environment:
      - _APP_ENV
      - _APP_OPENSSL_KEY_V1
      - _APP_REDIS_HOST
      - _APP_REDIS_PORT
      - _APP_REDIS_USER
      - _APP_REDIS_PASS
      - _APP_DB_HOST
      - _APP_DB_PORT
      - _APP_DB_SCHEMA
      - _APP_DB_USER
      - _APP_DB_PASS
      - _APP_LOGGING_PROVIDER
      - _APP_LOGGING_CONFIG

  appwrite-worker-builds:
    image: appwrite/appwrite:1.3.8
    entrypoint: worker-builds
    <<: *x-logging
    container_name: appwrite-worker-builds
    restart: unless-stopped
    networks:
      - appwrite
    environment:
      - _APP_ENV
      - _APP_OPENSSL_KEY_V1
      - _APP_EXECUTOR_SECRET
      - _APP_EXECUTOR_HOST
      - _APP_REDIS_HOST
      - _APP_REDIS_PORT
      - _APP_REDIS_USER
      - _APP_REDIS_PASS
      - _APP_DB_HOST
      - _APP_DB_PORT
      - _APP_DB_SCHEMA
      - _APP_DB_USER
      - _APP_DB_PASS
      - _APP_LOGGING_PROVIDER
      - _APP_LOGGING_CONFIG

  appwrite-worker-certificates:
    image: appwrite/appwrite:1.3.8
    entrypoint: worker-certificates
    <<: *x-logging
    container_name: appwrite-worker-certificates
    restart: unless-stopped
    networks:
      - appwrite
    volumes:
      - appwrite-config:/storage/config:rw
      - appwrite-certificates:/storage/certificates:rw
    environment:
      - _APP_ENV
      - _APP_OPENSSL_KEY_V1
      - _APP_DOMAIN
      - _APP_DOMAIN_TARGET
      - _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
      - _APP_REDIS_HOST
      - _APP_REDIS_PORT
      - _APP_REDIS_USER
      - _APP_REDIS_PASS
      - _APP_DB_HOST
      - _APP_DB_PORT
      - _APP_DB_SCHEMA
      - _APP_DB_USER
      - _APP_DB_PASS
      - _APP_LOGGING_PROVIDER
      - _APP_LOGGING_CONFIG

  appwrite-worker-functions:
    image: appwrite/appwrite:1.3.8
    entrypoint: worker-functions
    <<: *x-logging
    container_name: appwrite-worker-functions
    restart: unless-stopped
    networks:
      - appwrite
    depends_on:
      - appwrite-executor
    environment:
      - _APP_ENV
      - _APP_OPENSSL_KEY_V1
      - _APP_REDIS_HOST
      - _APP_REDIS_PORT
      - _APP_REDIS_USER
      - _APP_REDIS_PASS
      - _APP_DB_HOST
      - _APP_DB_PORT
      - _APP_DB_SCHEMA
      - _APP_DB_USER
      - _APP_DB_PASS
      - _APP_FUNCTIONS_TIMEOUT
      - _APP_EXECUTOR_SECRET
      - _APP_EXECUTOR_HOST
      - _APP_USAGE_STATS
      - DOCKERHUB_PULL_USERNAME
      - DOCKERHUB_PULL_PASSWORD

  appwrite-executor:
    image: appwrite/appwrite:1.3.8
    entrypoint: executor
    <<: *x-logging
    container_name: appwrite-executor
    restart: unless-stopped
    stop_signal: SIGINT
    networks:
      appwrite:
      runtimes:
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - appwrite-functions:/storage/functions:rw
      - appwrite-builds:/storage/builds:rw
      - /tmp:/tmp:rw
    depends_on:
      - appwrite
    environment:
      - _APP_ENV
      - _APP_VERSION
      - _APP_FUNCTIONS_TIMEOUT
      - _APP_FUNCTIONS_BUILD_TIMEOUT
      - _APP_FUNCTIONS_CONTAINERS
      - _APP_FUNCTIONS_RUNTIMES
      - _APP_FUNCTIONS_CPUS
      - _APP_FUNCTIONS_MEMORY
      - _APP_FUNCTIONS_MEMORY_SWAP
      - _APP_FUNCTIONS_INACTIVE_THRESHOLD
      - _APP_EXECUTOR_SECRET
      - OPEN_RUNTIMES_NETWORK
      - _APP_LOGGING_PROVIDER
      - _APP_LOGGING_CONFIG
      - _APP_STORAGE_DEVICE
      - _APP_STORAGE_S3_ACCESS_KEY
      - _APP_STORAGE_S3_SECRET
      - _APP_STORAGE_S3_REGION
      - _APP_STORAGE_S3_BUCKET
      - _APP_STORAGE_DO_SPACES_ACCESS_KEY
      - _APP_STORAGE_DO_SPACES_SECRET
      - _APP_STORAGE_DO_SPACES_REGION
      - _APP_STORAGE_DO_SPACES_BUCKET
      - _APP_STORAGE_BACKBLAZE_ACCESS_KEY
      - _APP_STORAGE_BACKBLAZE_SECRET
      - _APP_STORAGE_BACKBLAZE_REGION
      - _APP_STORAGE_BACKBLAZE_BUCKET
      - _APP_STORAGE_LINODE_ACCESS_KEY
      - _APP_STORAGE_LINODE_SECRET
      - _APP_STORAGE_LINODE_REGION
      - _APP_STORAGE_LINODE_BUCKET
      - _APP_STORAGE_WASABI_ACCESS_KEY
      - _APP_STORAGE_WASABI_SECRET
      - _APP_STORAGE_WASABI_REGION
      - _APP_STORAGE_WASABI_BUCKET
      - DOCKERHUB_PULL_USERNAME
      - DOCKERHUB_PULL_PASSWORD

  appwrite-worker-mails:
    image: appwrite/appwrite:1.3.8
    entrypoint: worker-mails
    <<: *x-logging
    container_name: appwrite-worker-mails
    restart: unless-stopped
    networks:
      - appwrite
    environment:
      - _APP_ENV
      - _APP_OPENSSL_KEY_V1
      - _APP_SYSTEM_EMAIL_NAME
      - _APP_SYSTEM_EMAIL_ADDRESS
      - _APP_REDIS_HOST
      - _APP_REDIS_PORT
      - _APP_REDIS_USER
      - _APP_REDIS_PASS
      - _APP_SMTP_HOST
      - _APP_SMTP_PORT
      - _APP_SMTP_SECURE
      - _APP_SMTP_USERNAME
      - _APP_SMTP_PASSWORD
      - _APP_LOGGING_PROVIDER
      - _APP_LOGGING_CONFIG

  appwrite-worker-messaging:
    image: appwrite/appwrite:1.3.8
    entrypoint: worker-messaging
    <<: *x-logging
    container_name: appwrite-worker-messaging
    restart: unless-stopped
    networks:
      - appwrite
    environment:
      - _APP_ENV
      - _APP_REDIS_HOST
      - _APP_REDIS_PORT
      - _APP_REDIS_USER
      - _APP_REDIS_PASS
      - _APP_SMS_PROVIDER
      - _APP_SMS_FROM
      - _APP_LOGGING_PROVIDER
      - _APP_LOGGING_CONFIG

  appwrite-maintenance:
    image: appwrite/appwrite:1.3.8
    entrypoint: maintenance
    <<: *x-logging
    container_name: appwrite-maintenance
    restart: unless-stopped
    networks:
      - appwrite
    environment:
      - _APP_ENV
      - _APP_OPENSSL_KEY_V1
      - _APP_DOMAIN
      - _APP_DOMAIN_TARGET
      - _APP_REDIS_HOST
      - _APP_REDIS_PORT
      - _APP_REDIS_USER
      - _APP_REDIS_PASS
      - _APP_DB_HOST
      - _APP_DB_PORT
      - _APP_DB_SCHEMA
      - _APP_DB_USER
      - _APP_DB_PASS
      - _APP_MAINTENANCE_INTERVAL
      - _APP_MAINTENANCE_RETENTION_EXECUTION
      - _APP_MAINTENANCE_RETENTION_CACHE
      - _APP_MAINTENANCE_RETENTION_ABUSE
      - _APP_MAINTENANCE_RETENTION_AUDIT
      - _APP_MAINTENANCE_RETENTION_USAGE_HOURLY

  appwrite-usage:
    image: appwrite/appwrite:1.3.8
    entrypoint: usage
    container_name: appwrite-usage
    <<: *x-logging
    restart: unless-stopped
    networks:
      - appwrite
    environment:
      - _APP_ENV
      - _APP_OPENSSL_KEY_V1
      - _APP_DB_HOST
      - _APP_DB_PORT
      - _APP_DB_SCHEMA
      - _APP_DB_USER
      - _APP_DB_PASS
      - _APP_USAGE_AGGREGATION_INTERVAL
      - _APP_REDIS_HOST
      - _APP_REDIS_PORT
      - _APP_REDIS_USER
      - _APP_REDIS_PASS
      - _APP_LOGGING_PROVIDER
      - _APP_LOGGING_CONFIG

  appwrite-schedule:
    image: appwrite/appwrite:1.3.8
    entrypoint: schedule
    container_name: appwrite-schedule
    <<: *x-logging
    restart: unless-stopped
    networks:
      - appwrite
    environment:
      - _APP_ENV
      - _APP_REDIS_HOST
      - _APP_REDIS_PORT
      - _APP_REDIS_USER
      - _APP_REDIS_PASS

  telegraf:
    image: appwrite/telegraf:1.4.0
    container_name: appwrite-telegraf
    <<: *x-logging
    restart: unless-stopped
    networks:
      - appwrite
    environment:

networks:
  gateway:
  appwrite:
  runtimes:

volumes:
  appwrite-cache:
    driver: local
    driver_opts:
      type: volume
      o: "bind"
      device: "/home/share/cache"
  appwrite-uploads:
    driver: local
    driver_opts:
      type: volume
      o: "bind"
      device: "/home/share/uploads"
  appwrite-certificates:
    driver: local
    driver_opts:
      type: volume
      o: "bind"
      device: "/home/share/certificates"
  appwrite-functions:
    driver: local
    driver_opts:
      type: volume
      o: "bind"
      device: "/home/share/functions"
  appwrite-builds:
    driver: local
    driver_opts:
      type: volume
      o: "bind"
      device: "/home/share/builds"
  appwrite-config:
    driver: local
    driver_opts:
      type: volume
      o: "bind"
      device: "/home/share/config"
  appwrite-executor:
    driver: local
    driver_opts:
      type: volume
      o: "bind"
      device: "/home/share/executor"

Customize variables

Next you'll need to create .env file. You can use the regular one, you just need to replace the host for MariaDB, Redis and Influx DB with our Main server internal IP.

.env file

Notice the green lines in which the value 10.0.0.15 is used as the Main server internal IP.

Also, make sure to update the values in the red lines with you've set in the Main .env file

ini
_APP_ENV = production
_APP_LOCALE = en
_APP_OPTIONS_ABUSE = enabled
_APP_OPTIONS_FORCE_HTTPS = disabled
_APP_OPENSSL_KEY_V1 = your-secret-key
_APP_DOMAIN = localhost
_APP_DOMAIN_TARGET = localhost
_APP_CONSOLE_WHITELIST_ROOT = enabled
_APP_CONSOLE_WHITELIST_EMAILS =
_APP_CONSOLE_WHITELIST_IPS =
_APP_SYSTEM_EMAIL_NAME = Appwrite
_APP_SYSTEM_EMAIL_ADDRESS = [email protected]
_APP_SYSTEM_RESPONSE_FORMAT =
_APP_SYSTEM_SECURITY_EMAIL_ADDRESS = [email protected]
_APP_USAGE_STATS = enabled
_APP_LOGGING_PROVIDER =
_APP_LOGGING_CONFIG =
_APP_USAGE_AGGREGATION_INTERVAL = 30
_APP_USAGE_TIMESERIES_INTERVAL = 30
_APP_USAGE_DATABASE_INTERVAL = 900
_APP_WORKER_PER_CORE = 6
_APP_REDIS_HOST = 10.0.0.15 
_APP_REDIS_PORT = 6379
_APP_REDIS_USER =
_APP_REDIS_PASS =
_APP_DB_HOST = 10.0.0.15 
_APP_DB_PORT = 3306
_APP_DB_SCHEMA = appwrite
_APP_DB_USER = user 
_APP_DB_PASS = password 
_APP_DB_ROOT_PASS = rootsecretpassword 
_APP_STATSD_HOST = telegraf
_APP_STATSD_PORT = 8125
_APP_SMTP_HOST =
_APP_SMTP_PORT =
_APP_SMTP_SECURE =
_APP_SMTP_USERNAME =
_APP_SMTP_PASSWORD =
_APP_SMS_PROVIDER =
_APP_SMS_FROM =
_APP_STORAGE_LIMIT = 30000000
_APP_STORAGE_PREVIEW_LIMIT = 20000000
_APP_STORAGE_ANTIVIRUS = disabled
_APP_STORAGE_ANTIVIRUS_HOST = clamav
_APP_STORAGE_ANTIVIRUS_PORT = 3310
_APP_STORAGE_DEVICE = local
_APP_STORAGE_S3_ACCESS_KEY =
_APP_STORAGE_S3_SECRET =
_APP_STORAGE_S3_REGION = us-east-1
_APP_STORAGE_S3_BUCKET =
_APP_STORAGE_DO_SPACES_ACCESS_KEY =
_APP_STORAGE_DO_SPACES_SECRET =
_APP_STORAGE_DO_SPACES_REGION = us-east-1
_APP_STORAGE_DO_SPACES_BUCKET =
_APP_STORAGE_BACKBLAZE_ACCESS_KEY =
_APP_STORAGE_BACKBLAZE_SECRET =
_APP_STORAGE_BACKBLAZE_REGION = us-west-004
_APP_STORAGE_BACKBLAZE_BUCKET =
_APP_STORAGE_LINODE_ACCESS_KEY =
_APP_STORAGE_LINODE_SECRET =
_APP_STORAGE_LINODE_REGION = eu-central-1
_APP_STORAGE_LINODE_BUCKET =
_APP_STORAGE_WASABI_ACCESS_KEY =
_APP_STORAGE_WASABI_SECRET =
_APP_STORAGE_WASABI_REGION = eu-central-1
_APP_STORAGE_WASABI_BUCKET =
_APP_FUNCTIONS_SIZE_LIMIT = 30000000
_APP_FUNCTIONS_TIMEOUT = 900
_APP_FUNCTIONS_BUILD_TIMEOUT = 900
_APP_FUNCTIONS_CONTAINERS = 10
_APP_FUNCTIONS_CPUS = 0
_APP_FUNCTIONS_MEMORY = 0
_APP_FUNCTIONS_MEMORY_SWAP = 0
_APP_FUNCTIONS_RUNTIMES = node-16.0,php-8.0,python-3.9,ruby-3.0
_APP_EXECUTOR_SECRET = your-secret-key
_APP_EXECUTOR_HOST = http://appwrite-executor/v1
_APP_EXECUTOR_RUNTIME_NETWORK = appwrite_runtimes
_APP_FUNCTIONS_ENVS = node-16.0,php-7.4,python-3.9,ruby-3.0
_APP_FUNCTIONS_INACTIVE_THRESHOLD = 60
DOCKERHUB_PULL_USERNAME =
DOCKERHUB_PULL_PASSWORD =
DOCKERHUB_PULL_EMAIL =
OPEN_RUNTIMES_NETWORK = appwrite_runtimes
_APP_MAINTENANCE_INTERVAL = 86400
_APP_MAINTENANCE_RETENTION_CACHE = 2592000
_APP_MAINTENANCE_RETENTION_EXECUTION = 1209600
_APP_MAINTENANCE_RETENTION_AUDIT = 1209600
_APP_MAINTENANCE_RETENTION_ABUSE = 86400
_APP_MAINTENANCE_RETENTION_USAGE_HOURLY = 8640000
_APP_GRAPHQL_MAX_BATCH_SIZE = 10
_APP_GRAPHQL_MAX_COMPLEXITY = 250
_APP_GRAPHQL_MAX_DEPTH = 3

Now you can start the docker compose by running:

shell
docker compose up -d

Vertical scaling

Even though you're going to use decentralization with horizontal scaling, decentralization on itself gives a huge advantage even when working with a single server of Appwrite, by letting you scale up and down your server with zero downtime.

The way you can do that is by doing something like this:

  • Take a snapshot of our current single Client server that connects to the Main one.
  • Create a new - bigger or smaller - server using that snapshot.
  • Point all DNS A record requests to the newly created server.
  • Wait till your DNS TTL has passed.
  • Now you can safely delete the old server.

This way you've scaled your server with zero downtime.

Horizontal scaling

Continue to the next chapter to see how you can scale our Appwrite infrastructure horizontally.