Deployment guide

Hosting Appwrite on a VPS: complete self-hosted guide

Deploy on a VPS Cloud →

Tutorial

Hosting Appwrite on a VPS: complete self-hosted guide

Databases10 min read6 steps

Appwrite is an open source backend-as-a-service designed for web and mobile developers: document database, authentication, file storage, serverless functions, and messaging. Self-hosting it on a VPS gives you a complete self-hosted backend, with multi-platform SDKs and no usage-based billing. This guide covers installation, Traefik configuration, production hardening, and backups — from A to Z.

Contents· Why self-host Appwrite on a VPS1/10
  1. 01Why self-host Appwrite on a VPS
  2. 02The concrete benefits of a self-hosted Appwrite
  3. 03Internal architecture: what Docker actually launches
  4. 04Hardware and software prerequisites
  5. 05Deploying self-hosted Appwrite with Docker
  6. 06Networking and Traefik: what happens behind the scenes
  7. 07Troubleshooting common issues
  8. 08Appwrite vs Supabase: which one to self-host?
  9. 09Updates and ongoing maintenance
  10. 10This template ships the core — Functions come later

Why self-host Appwrite on a VPS

Appwrite brings together in a single platform everything an application needs on the server side: authentication with more than 30 OAuth methods, a document database with granular permissions, file storage with image transformation, serverless functions in several languages, and messaging. Its cloud version bills per usage and per resources; by self-hosting on a VPS, you deploy the whole thing via Docker and drive it from an elegant web console, with no cap.

It's particularly suited to Flutter, React Native, or web developers who want a ready-to-use backend with official SDKs, while keeping the data and logic on their own infrastructure. You control the functions, the API keys, and the permissions, and you scale at your own pace — without being subject to the quotas or price increases of a public cloud.

The concrete benefits of a self-hosted Appwrite

  • All-in-one backend: Auth, Databases, Storage, Functions, and Messaging in a single console.
  • Official SDKs for Flutter, React Native, Web, Android, iOS, Node, Python, and more.
  • More than 30 built-in OAuth providers (Google, GitHub, Apple...) with no code.
  • Self-hosted serverless functions: run your business logic without an external service.
  • Granular permissions at the document and collection level, managed from the console.
  • No billing per execution and no user quota: a fixed VPS plan.
  • Data hosted on your own infrastructure: simplified GDPR compliance, no third-party cloud dependency.
  • Built-in Realtime: subscribe to changes in documents, files, and session state via WebSocket.

Internal architecture: what Docker actually launches

Before installing, it helps to know what Appwrite orchestrates. The official installer generates a docker-compose.yml with at least twenty services:

- appwrite — the main API server (PHP, exposed on port 80/443 via Traefik).
- appwrite-console — the web administration console (React, served at the same domain on /).
- appwrite-realtime — the WebSocket service for real-time subscriptions.
- appwrite-worker-* — a series of workers for asynchronous tasks: sending e-mails, outbound webhooks, temporary file cleanup, etc.
- appwrite-executor — the serverless Functions execution runtime (Docker-in-Docker).
- mariadb — the internal relational database (users, projects, permission rules).
- redis — the cache and task queue (worker jobs, sessions, rate-limiting).
- traefik — the built-in reverse proxy that handles HTTPS routing and Let's Encrypt certificates.

For a development VPS, only the core node is strictly necessary: API, Console, MariaDB, Redis, and Traefik. The workers and executor are indispensable in production if you use Functions, webhooks, or e-mail sending. Count on at least 4 GB of RAM for this full stack: MariaDB and the background workers share the available memory, and each Function invocation starts its own ephemeral container.

Hardware and software prerequisites

For a development or test instance, 2 vCPU and 4 GB of RAM with 40 GB of SSD are enough. For production with active serverless functions and several client apps, aim for 4 vCPU, 8 GB of RAM, and 80 GB of SSD — each Function invocation starts its own ephemeral container, which puts pressure on disk and CPU.

On the software side: Ubuntu 22.04 or 24.04 LTS, Docker CE and Docker Compose v2 (Appwrite manages its own Traefik for routing and SSL), and a domain name pointing to the VPS IP — for example api.myapp.com. Ports 80 and 443 must be free and open in ufw for Traefik to obtain the Let's Encrypt certificates via the ACME challenge.

A note on storage. Functions generate intermediate Docker images that accumulate on the SSD. Without regular pruning, an instance running for several months can saturate its disk. Plan a weekly docker system prune --volumes -f cron task, and monitor free space with df -h.

Deploying self-hosted Appwrite with Docker

  1. Prepare the VPS and open the ports

    Install Docker CE and Docker Compose v2, then open the required ports:

    ufw allow 80/tcp
    ufw allow 443/tcp
    ufw enable

    Point your domain (e.g., api.myapp.com) to the VPS IP via an A record. Wait for DNS propagation before running the installer — Traefik needs the domain to resolve correctly in order to pass the ACME challenge.

  2. Run the official interactive installer

    Run the installer with one command:

    docker run -it --rm \
      --volume /var/run/docker.sock:/var/run/docker.sock \
      --volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
      --entrypoint="install" \
      appwrite/appwrite

    The wizard asks for the main domain, HTTP port (80), HTTPS port (443), an e-mail for Let's Encrypt, and a few options. It generates a docker-compose.yml and a .env file with randomly generated secrets. Do not change these secrets by hand — modify them only via the environment variables in .env.

  3. Verify the domain and SSL configuration

    After startup (docker compose up -d), wait 30 to 60 seconds for Traefik to issue the certificate. Check:

    curl -I https://api.myapp.com/v1/health

    The response should be HTTP/2 200 with a content-type: application/json. If Traefik does not yet have a certificate, you'll get a TLS error — this is normal during the first few seconds. If it persists, check that ports 80/443 are not filtered by an upstream firewall and that the domain resolves to the correct IP (dig api.myapp.com).

  4. Create the administrator account and the first project

    Open https://api.myapp.com in your browser. Appwrite invites you to create the root administrator account on first access — this account is the only one with access to platform settings. From the console, create a project, add a platform (Web, Flutter, Apple…) by declaring the allowed hostname, and retrieve the project ID. This information feeds the client-side SDK initialization:

    npm install appwrite
    import { Client } from 'appwrite';
    const client = new Client()
      .setEndpoint('https://api.myapp.com/v1')
      .setProject('<your-project-id>');
  5. Harden the production configuration

    Open the .env file generated by the installer and adjust the following variables:

    - _APP_OPTIONS_ABUSE=enabled — activates built-in per-IP rate-limiting.
    - _APP_OPTIONS_FORCE_HTTPS=enabled — redirects all HTTP requests to HTTPS.
    - _APP_SMTP_HOST / _APP_SMTP_PORT / _APP_SMTP_SECURE / _APP_SMTP_USERNAME / _APP_SMTP_PASSWORD — configure your SMTP relay for verification and password-reset e-mails.
    - _APP_CONSOLE_WHITELIST_ROOT=disabled if you want to allow open registration to the console (not recommended in prod); leave it on enabled and fill in _APP_CONSOLE_WHITELIST_EMAILS with the authorized addresses.

    Restart the services after modification: docker compose up -d.

  6. Configure automated backups

    Two Docker volumes hold all of Appwrite's persistent data:

    - appwrite-mariadb — the database (users, projects, documents, rules).
    - appwrite-uploads — files uploaded by client apps.

    Schedule a daily dump:

    docker exec appwrite-mariadb mysqldump \
      -u appwrite -p"$MARIADB_ROOT_PASSWORD" appwrite \
      > /backups/appwrite-$(date +%Y%m%d).sql

    Also archive the appwrite-uploads volume (a tar of the data or a volume snapshot depending on your infrastructure). Also back up _APP_OPENSSL_KEY_V1 — Appwrite uses it to encrypt files and some sensitive data. Without this key, a database restore makes the encrypted content permanently unreadable.

Networking and Traefik: what happens behind the scenes

Traefik is Appwrite's only network entry point: it listens on ports 80 and 443, manages Let's Encrypt certificates, and routes requests to the correct internal service. This integration is transparent — you don't write any Traefik configuration, it's generated by the installer.

A few important points if you add an upstream reverse proxy (nginx, Cloudflare Tunnel…):

- Do not double the TLS. If your upstream proxy already terminates SSL and forwards in HTTP to Appwrite, disable the ACME challenge by setting _APP_OPTIONS_FORCE_HTTPS=disabled and leaving _APP_SYSTEM_SECURITY_EMAIL_ADDRESS= empty to prevent Traefik from trying to obtain a certificate it cannot validate.
- Forward the real IP. Add X-Forwarded-For and X-Real-IP in your upstream proxy, and enable header trust in Traefik (already configured by default in the Appwrite installer).
- Console ports. The web console and the API are served under the same domain: do not route port 9501 (internal Realtime) publicly — it is managed internally by Traefik.

If you place Appwrite behind Cloudflare in proxied mode, set the zone's SSL mode to Full (strict) — in Flexible mode, nginx redirects HTTP→HTTPS, which creates an infinite redirect loop between Cloudflare and Traefik.

Troubleshooting common issues

The console shows "Could not connect to the Appwrite API".
Check that the _APP_DOMAIN and _APP_DOMAIN_TARGET variables in .env exactly match the domain you are using. A misconfigured subdomain (with or without www) prevents Traefik from routing correctly. Run docker compose up -d after any .env modification.

Verification e-mails are not arriving.
Appwrite has no built-in SMTP relay — you must configure _APP_SMTP_* with an external service (Mailgun, Resend, your own server). Without SMTP configured, e-mails are queued and ignored. Check the logs of the appwrite-worker-mails worker: docker compose logs appwrite-worker-mails --tail 50.

Serverless Functions time out.
Each Function starts its own Docker container. On a VPS with 2 GB of RAM, the cold start often exceeds 10 seconds, which triggers the default timeout. Increase _APP_FUNCTIONS_TIMEOUT in .env and plan for at least 4 GB of RAM for production use. Also monitor disk space — runtime images stay on the SSD after execution.

The SSL certificate is not being generated.
Traefik needs ports 80 and 443 to respond from the outside in order to pass the ACME challenge. If an upstream firewall (your VPS provider's security rules) blocks these ports, the challenge fails silently. Test from an external machine: curl -I http://api.myapp.com should respond (even with a 302), not time out.

MariaDB starts but the console shows database errors.
If you have manually modified .env between restarts, check that _APP_DB_HOST, _APP_DB_USER, and _APP_DB_PASS match the values in the MariaDB container. A partial restart (a single service) without docker compose down can leave orphaned containers with old variables in memory.

Appwrite vs Supabase: which one to self-host?

Scroll the table

CriterionAppwriteSupabase
DatabaseMariaDB, document modelNative PostgreSQL, full SQL
ApproachSDK- and mobile-app-orientedSQL- and relational-app-oriented
Serverless functionsNative, multi-language built-inEdge Functions (Deno)
InstallationOfficial interactive installerCompose to configure manually
SSL / routingBuilt-in Traefik, automaticExternal reverse proxy to add
Vector / AI searchNot nativeNative via pgvector
RealtimeBuilt-in WebSocket, multi-resourcePostgres CDC via Realtime
Ideal forFlutter, React Native, mobile-firstAdvanced SQL, RAG, relational
Administration consoleRich, guided consoleStudio focused on SQL tables

Updates and ongoing maintenance

To update Appwrite, re-run the installer with the new version or modify the image tag in docker-compose.yml and run docker compose pull && docker compose up -d. Appwrite applies schema migrations automatically when the API container starts — no manual command is needed.

Also schedule a weekly purge of orphaned Docker images to avoid SSD saturation:

docker system prune --volumes -f

Back up _APP_OPENSSL_KEY_V1 in a separate vault (secrets manager, encrypted file outside the VPS). Without this key, a database restore makes encrypted files and some sensitive data permanently unreadable — it is the only point of no return in an Appwrite installation.

This template ships the core — Functions come later

The official Appwrite installer generates 25+ containers, including background workers and an execution runtime for serverless Functions and Sites. To keep the VPS footprint small, this template ships only the self-consistent core — the API server, MariaDB, Redis and Traefik — which fully powers Auth, Databases, Storage, Realtime and the Console. Serverless Functions, outbound webhooks and background e-mail need the extra worker and executor containers and are not enabled here. When your project needs them, grow to Appwrite's full official docker-compose stack and plan for 4 GB+ of RAM. For backups, snapshot the appwrite-mariadb (data) and appwrite-uploads (files) Docker volumes on a schedule.

Self-host your Appwrite backend

The ServOrbit Cloud VPS with a ready-to-use Docker template and SSD storage hosts the entire Appwrite stack, serverless functions included, over HTTPS.

Need help?

Browse our help center and FAQ, or reach our team — callback, WhatsApp or email. Support in French, English and Arabic.

Message us on WhatsAppopens in a new tab