Skip to main contentSkip to navigation
[email protected]
Client AreaSupport
Hosting Mammoth
HostingMammothYour Data, Our Responsibility
Home
Solutions
Hosting Services
Store
Pricing
About
Blog
API
Contact

Stay Ahead of the Curve

Get the latest insights on cybersecurity, AI innovations, and enterprise data solutions delivered to your inbox.

Hosting Mammoth
HostingMammothEnterprise Solutions

Enterprise-grade data solutions. Hosting, recovery, cybersecurity, and AI-powered services for businesses worldwide.

[email protected]
Sun - Fri, 9:00am - 5:00pm

Services

  • Cloud Hosting
  • Data Recovery
  • Cybersecurity
  • Legal Support
  • MSP Services
  • Web Development
  • AI Services
  • Free Server Migration

Hosting

  • VPS Hosting (NVMe SSD)
  • VDS Hosting (NVMe)
  • Storage VPS (High SSD)
  • GPU Servers
  • Managed Services
  • Cloud Firewall
  • Load Balancer
  • One-Click Apps
  • n8n Hosting
  • Object Storage
  • FAQ

Company

  • Store
  • Pricing
  • About Us
  • Locations
  • Blog
  • Testimonials
  • Contact
  • Affiliate Program
  • White-Label
  • Terms of Service
  • Privacy Policy
  • Browser Cookies
  • SLA

Support

  • Client Area
  • Submit Ticket
  • Knowledge Base
  • Server Status
  • API Documentation

© 2026 Hosting Mammoth. All rights reserved.

Knowledge Base
Getting StartedAccount ManagementVPS HostingGPU ServersStorage VPSCloud FirewallLoad BalancerServer ManagementBilling & PaymentsSupport & TicketsAffiliate ProgramReseller ProgramMarketplace & Appsn8n HostingManaged ServicesServer MigrationAPI & DevelopersSecurityTroubleshootingGlossaryInstall Guides
  1. Home
  2. /
  3. Support
  4. /
  5. Install Guides
  6. /
  7. How To Install Appwrite Ubuntu
GUIDEInstall Guides

How to Install Appwrite on Ubuntu 24.04 VPS: Open-Source Backend as a Service

33 min read

How to Install Appwrite on Ubuntu 24.04 VPS: Open-Source Backend as a Service

Appwrite is the leading open-source Backend-as-a-Service platform, offering a batteries-included alternative to Firebase and Supabase that you can run on your own infrastructure. This tutorial walks you through a full production install on an Ubuntu 24.04 VPS, from the one-line Docker bootstrapper to a hardened deployment with Traefik auto-TLS, a custom domain, your first project, databases, cloud functions, auth providers, storage buckets, webhooks, and SDK integration.

Skip the manual work? Deploy a pre-tuned backend VPS on our CloudCore Professional plan with Docker and Traefik already configured, so you can focus on shipping your app.

Table of Contents

  • What is Appwrite?
  • Why Self-Host Appwrite vs Firebase or Supabase Cloud?
  • Prerequisites
  • Step 1: Prepare the Ubuntu 24.04 VPS
  • Step 2: Install Docker and Docker Compose
  • Step 3: Point Your Domain at the Server
  • Step 4: Run the Appwrite Installer
  • Step 5: Configure the .env File
  • Step 6: Enable Traefik Auto TLS with Let's Encrypt
  • Step 7: Create the Admin Console Account
  • Step 8: Create Your First Project
  • Step 9: Set Up Databases and Collections
  • Step 10: Configure Authentication Providers
  • Step 11: Create Storage Buckets
  • Step 12: Deploy Your First Function
  • Step 13: Configure Webhooks
  • Step 14: Integrate an SDK
  • Post-Install: Backups and Upgrades
  • Troubleshooting
  • FAQ
  • Next Steps
  • What is Appwrite?

    Appwrite is an open-source backend server that bundles authentication, databases, storage, serverless functions, realtime subscriptions, messaging, and a CDN-style file API behind a single REST, GraphQL, and WebSocket interface. It runs as a set of Docker containers orchestrated by Docker Compose, with Traefik handling ingress and TLS, MariaDB storing metadata, Redis caching hot data, and InfluxDB plus Telegraf powering usage metrics. Every feature you would normally wire up from scratch (password hashing, JWT signing, OAuth dance handling, S3-compatible storage, cron workers, email queues) ships ready to use.

    Under the hood, Appwrite is built in PHP with Swoole for the API layer, and each runtime language for cloud functions is its own sandboxed Docker image. You interact with it through an admin console (a React-based web UI at https://your-domain.com/console), through server SDKs for Node.js, Python, PHP, Go, Ruby, Deno, Swift, Kotlin, Dart, and .NET, or through client SDKs for Web, Flutter, iOS, Android, and React Native. The same APIs power your mobile apps, single-page apps, backend services, and serverless functions without you writing boilerplate auth or CRUD code.

    Typical use cases include full-stack web apps that need user accounts plus a document database, Flutter and React Native mobile apps that want Firebase-like realtime sync, marketing teams that need a file upload and transformation API, and SaaS founders who want to ship an MVP in a weekend without managing half a dozen cloud services. Appwrite scales from a single-node VPS install (perfect for MVPs and internal tools) up to multi-node Kubernetes deployments serving millions of users.

    Why Self-Host Appwrite vs Firebase or Supabase Cloud?

    The BaaS market is crowded, and each option makes different tradeoffs. Running Appwrite on your own VPS buys you specific advantages over Firebase, Supabase Cloud, and even Appwrite Cloud:

    • Flat, predictable cost. Firebase Firestore reads, writes, bandwidth, storage, and Cloud Functions invocations are all metered. A successful launch can surprise you with a four-figure bill. A VPS costs the same whether you serve 100 users or 100,000 — you only upgrade hardware when you actually outgrow it.
    • Complete data residency and GDPR control. Your users' emails, password hashes, files, and database rows live on one server in the region you chose, not spread across Google's or AWS's global footprint. You sign your own DPA because you are the processor.
    • No vendor lock-in. Firebase Firestore's query model and security rules don't port anywhere. Appwrite's data layer is standard SQL (MariaDB) underneath, and everything is open source under BSD-3. If you ever want to leave, you take your whole stack with you.
    • Unlimited functions, projects, and users. Appwrite Cloud's free tier caps projects, function executions, bandwidth, and storage. Self-hosted, the only cap is your hardware.
    • Run next to your app. If your frontend and your Appwrite instance live in the same data center, every auth check and database read is a sub-millisecond local network call instead of a cross-continent round trip.
    • Custom runtimes and binaries. Want a function that calls ffmpeg, shells out to a Python ML model, or uses a headless Chromium? Self-hosted Appwrite lets you extend runtimes and mount volumes. Cloud BaaS providers don't.
    • Open source all the way down. The entire codebase is on GitHub. You can patch bugs, audit security, and contribute features — something no proprietary BaaS permits.

    Cost and Capability Comparison

    FeatureFirebase (Blaze)Supabase Cloud (Pro)Appwrite Cloud (Pro)Self-Hosted Appwrite (VPS)
    Monthly base costPay-as-you-go$25/mo$15/moEUR 19.99/mo (flat)
    Database reads$0.06 / 100K8 GB incl.3.5M executionsUnlimited
    Auth users50K free, then metered100K MAUUnlimitedUnlimited
    Storage$0.026 / GB100 GB150 GBDisk-limited
    Bandwidth$0.12 / GB250 GB300 GBUnmetered on most VPS
    Function invocationsMeteredMetered3.5MUnlimited
    Data residency controlRegional onlyRegionalRegionalSingle server, you pick
    Custom runtimesNoEdge functions onlyFixed listFull Docker, any runtime
    Source-availableNoYesYesYes (BSD-3)
    For most MVPs and mid-scale apps, self-hosted Appwrite on a single VPS delivers the same feature set as Firebase Blaze at roughly 5-10% of the cost, with none of the metering anxiety.

    Prerequisites

    Before you begin, make sure you have:

    • A VPS running Ubuntu 24.04 LTS with root or sudo access.
    • SSH access to the server.
    • A registered domain (for example, appwrite.yourdomain.com) with the ability to edit DNS records.
    • At least 4 GB of RAM, 2 vCPU, and 30 GB of disk — Appwrite runs 12+ containers including MariaDB, Redis, InfluxDB, and per-runtime function pools.
    • Ports 80 and 443 open on your server firewall and at the provider level.
    Recommended Plan: CloudCore Professional
    >
    For a comfortable Appwrite deployment with headroom for function runtimes and growth, we recommend the CloudCore Professional plan:
    >
    - 6 vCPU cores
    - 12 GB RAM
    - 100 GB NVMe SSD
    - Unmetered bandwidth
    - EUR 19.99/month
    >
    This gives you the resources to run Appwrite alongside your app containers, a staging environment, or additional services like a Redis cache or background workers.

    If you need a Docker walkthrough first, read our How to Install Docker on Ubuntu guide before continuing.

    Connect via SSH:

    bash
    ssh root@your-server-ip

    Step 1: Prepare the Ubuntu 24.04 VPS

    Update the package index and upgrade installed packages so you have current security patches:

    bash
    sudo apt update && sudo apt upgrade -y

    Install a few utilities the rest of this guide uses:

    bash
    sudo apt install -y curl ca-certificates gnupg ufw git

    Configure the firewall to allow SSH, HTTP, and HTTPS:

    bash
    sudo ufw allow OpenSSH
    sudo ufw allow 80/tcp
    sudo ufw allow 443/tcp
    sudo ufw --force enable
    sudo ufw status

    Expected output:

    text
    Status: active

    To Action From -- ------ ---- OpenSSH ALLOW Anywhere 80/tcp ALLOW Anywhere 443/tcp ALLOW Anywhere

    Set the server's hostname (cosmetic, but helps with logs):

    bash
    sudo hostnamectl set-hostname appwrite

    If the kernel was upgraded, reboot once and reconnect:

    bash
    sudo reboot

    Step 2: Install Docker and Docker Compose

    Appwrite is distributed as a Docker Compose stack, so Docker Engine and the Compose plugin are the only runtime dependencies. Install them from Docker's official apt repository:

    bash
    curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
    bash
    echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu noble stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
    bash
    sudo apt update
    sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

    Enable and start Docker, then add your user to the docker group so you don't need sudo for every command:

    bash
    sudo systemctl enable --now docker
    sudo usermod -aG docker $USER
    newgrp docker

    Verify the install:

    bash
    docker --version
    docker compose version

    Expected output:

    text
    Docker version 27.3.1, build ce12230
    Docker Compose version v2.29.7

    For a complete Docker walkthrough including rootless mode, log rotation, and storage drivers, see How to Install Docker on Ubuntu.

    Step 3: Point Your Domain at the Server

    Appwrite ships with Traefik, which will automatically request a Let's Encrypt certificate when a request arrives on your chosen hostname. That only works if DNS already resolves to this server, so do this before the install.

    At your DNS provider (Cloudflare, Route 53, Namecheap, etc.), add an A record:

    TypeNameValueTTL
    Aappwriteyour-server-ip300
    If you also want a wildcard for future sub-routes or separate function domains, add:
    TypeNameValueTTL
    A*.appwriteyour-server-ip300
    If you use Cloudflare, set the proxy status to DNS only (grey cloud) initially. Orange-clouded records will cause Traefik's HTTP-01 challenge to fail unless you configure the DNS-01 method.

    Verify propagation before continuing:

    bash
    dig +short appwrite.yourdomain.com

    You should see the server's public IP returned. If not, wait 1-5 minutes and retry.

    Step 4: Run the Appwrite Installer

    Appwrite publishes a one-line installer script that pulls the latest stable release, generates secrets, writes a docker-compose.yml, and starts the stack. Run it from the directory where you want the install to live (the default is /usr/src/code/appwrite):

    bash
    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:1.6.0

    The installer will prompt you for a few values:

    text
    ? Enter your Appwrite HTTP port: (80)
    ? Enter your Appwrite HTTPS port: (443)
    ? Enter your Appwrite hostname: appwrite.yourdomain.com
    ? Enter a DNS A record hostname to serve as a CNAME for your custom domains:
      appwrite.yourdomain.com

    Accept the defaults for ports, set the hostname to the FQDN you pointed at the server in Step 3, and use the same value for the CNAME target.

    Expected final output:

    text
    Appwrite installed successfully

    The installer created a directory tree under ./appwrite/ on your host:

    text
    appwrite/
      .env
      docker-compose.yml

    It also pulled and started these containers (check with docker ps):

    • appwrite-traefik — ingress reverse proxy with Let's Encrypt
    • appwrite — main API server
    • appwrite-realtime — WebSocket server
    • appwrite-worker-* — background workers for mails, webhooks, functions, etc.
    • appwrite-mariadb — metadata and application data store
    • appwrite-redis — cache and queue
    • appwrite-influxdb — usage metrics
    • appwrite-telegraf — metrics collector
    • openruntimes-executor — sandboxed function runner
    Change into the install directory for the rest of this guide:

    bash
    cd appwrite

    Step 5: Configure the .env File

    The generated .env file is the single source of truth for all Appwrite settings. Open it in your editor:

    bash
    nano .env

    The keys you will almost always want to review or change are:

    bash
    # --- Hostname & URLs ---
    _APP_DOMAIN=appwrite.yourdomain.com
    _APP_DOMAIN_TARGET=appwrite.yourdomain.com
    _APP_DOMAIN_FUNCTIONS=functions.yourdomain.com

    --- Console account & email ---

    [email protected] _APP_CONSOLE_WHITELIST_IPS= _APP_SYSTEM_EMAIL_NAME=Appwrite [email protected]

    --- TLS ---

    _APP_OPTIONS_FORCE_HTTPS=enabled

    --- SMTP (required for password resets & team invites) ---

    _APP_SMTP_HOST=smtp.resend.com _APP_SMTP_PORT=587 _APP_SMTP_SECURE=tls _APP_SMTP_USERNAME=resend _APP_SMTP_PASSWORD=re_xxxxxxxxxxxxxxxxxxxxxxxxxxxx

    --- Storage ---

    _APP_STORAGE_LIMIT=30000000 _APP_STORAGE_DEVICE=local

    --- Functions ---

    _APP_FUNCTIONS_SIZE_LIMIT=30000000 _APP_FUNCTIONS_TIMEOUT=900 _APP_FUNCTIONS_BUILD_TIMEOUT=900 _APP_FUNCTIONS_CPUS=1 _APP_FUNCTIONS_MEMORY=512

    --- Security ---

    _APP_OPENSSL_KEY_V1=<auto-generated-32-byte-random-string>

    Key variables explained:

    • _APP_DOMAIN and _APP_DOMAIN_TARGET tell Traefik which hostname to serve and which hostname to use as the CNAME target when users attach custom domains to their projects.
    • _APP_DOMAIN_FUNCTIONS is the domain under which deployed functions expose HTTP endpoints (for example 64abc.functions.yourdomain.com). Point a wildcard A record at your server if you plan to use HTTP-triggered functions.
    • _APP_CONSOLE_WHITELIST_EMAILS restricts who can sign up for an admin account at /console. Set this to your email before the first login, otherwise anyone who finds your URL can register as an admin.
    • _APP_SMTP_* powers transactional mail (password resets, verification, team invites). Resend, Mailgun, Postmark, Amazon SES, and any standard SMTP service all work.
    • _APP_OPTIONS_FORCE_HTTPS=enabled makes Traefik redirect all HTTP traffic to HTTPS. Keep this on in production.
    • _APP_OPENSSL_KEY_V1 encrypts sensitive values in the database. The installer generates a random value — never change this after your first boot or existing encrypted data will be unrecoverable. Back it up alongside your database.
    After editing, apply the changes:

    bash
    docker compose up -d

    Only the containers affected by env changes will restart.

    Step 6: Enable Traefik Auto TLS with Let's Encrypt

    Appwrite's default docker-compose.yml already includes Traefik configured for Let's Encrypt HTTP-01 challenges. The first request to your hostname over HTTPS will trigger certificate issuance automatically — there is nothing to configure beyond having DNS pointed correctly.

    Verify Traefik picked up the hostname:

    bash
    docker logs appwrite-traefik 2>&1 | grep -i acme | tail -20

    You should see lines like:

    text
    "Starting ACME client" providerName=letsencrypt.acme
    "Trying to challenge certificate" domains=["appwrite.yourdomain.com"]
    "Register..." acmeCA=https://acme-v02.api.letsencrypt.org/directory
    "Certificates obtained" domains=["appwrite.yourdomain.com"]

    Open your browser and navigate to:

    text
    https://appwrite.yourdomain.com

    You should see the Appwrite login screen with a valid Let's Encrypt certificate. If the certificate is self-signed or you get an ERR_CERT error, the HTTP-01 challenge failed — see the Troubleshooting section below.

    Hardening Traefik

    For production, edit the Traefik service block in docker-compose.yml to disable the dashboard and force strict TLS. Find the traefik: service and ensure these command flags are present:

    yaml
    services:
      traefik:
        command:
          - --api.dashboard=false
          - --providers.docker=true
          - --providers.docker.exposedByDefault=false
          - --entrypoints.web.address=:80
          - --entrypoints.web.http.redirections.entrypoint.to=websecure
          - --entrypoints.web.http.redirections.entrypoint.scheme=https
          - --entrypoints.websecure.address=:443
          - --certificatesresolvers.letsencrypt.acme.email=admin@yourdomain.com
          - --certificatesresolvers.letsencrypt.acme.storage=/var/lib/traefik/acme.json
          - --certificatesresolvers.letsencrypt.acme.httpchallenge=true
          - --certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web

    Replace [email protected] with your own address — Let's Encrypt uses it for expiry notifications. Restart Traefik:

    bash
    docker compose up -d traefik

    Step 7: Create the Admin Console Account

    Visit https://appwrite.yourdomain.com/console/register and fill in:

    • Name — your display name
    • Email — must be in _APP_CONSOLE_WHITELIST_EMAILS
    • Password — minimum 8 characters, ideally stored in a password manager
    Click Sign Up. You are now the root admin for this Appwrite instance.

    Immediately after signup, update the whitelist to either just your email or a comma-separated list of your team's emails. Leaving it empty (the default) allows anyone to register.

    Then, enable multi-factor authentication on your admin account:

  • Click your avatar in the bottom left -> Account.
  • Open the Security tab.
  • Click Enable MFA and scan the QR code with any TOTP app (1Password, Authy, Google Authenticator).
  • Save the recovery codes in a password manager.
  • Step 8: Create Your First Project

    Appwrite is multi-tenant by design: a single instance hosts any number of independent projects, each with its own databases, auth, storage, and API keys.

    From the console:

  • Click Create project.
  • Enter a name (for example, my-saas-app).
  • Optionally enter a project ID — Appwrite will auto-generate one if left blank.
  • Click Create.
  • You land on the project overview. The sidebar shows Auth, Databases, Functions, Messaging, Storage, and Settings.

    Add a Platform

    A "platform" in Appwrite terminology is a client app that will talk to your API. The hostname on that platform must be registered or CORS will block requests.

  • Go to Overview -> Integrations -> Add platform.
  • Choose your client type (Web, Flutter, Apple, Android, or React Native).
  • For a web app, enter the hostname (app.yourdomain.com for production, localhost for development).
  • Click Next.
  • Appwrite will show you the endpoint URL (https://appwrite.yourdomain.com/v1) and the project ID — you'll need both in Step 14.

    Create a Server API Key

    For server-side operations (a Node.js backend, a deploy script, a migration), create an API key:

  • Go to Overview -> Integrations -> API keys -> Create API key.
  • Name it (for example, backend-server).
  • Set an expiration (or leave it at Never for a static service key).
  • Select the scopes this key needs — prefer the narrowest possible scope (for example, databases.read, databases.write, users.read).
  • Click Create.
  • Copy the secret immediately — it is shown once and then hashed.

    Step 9: Set Up Databases and Collections

    Appwrite's database layer gives you schema-defined document collections backed by MariaDB. Attributes are typed (string, integer, float, boolean, datetime, enum, IP, URL, email, relationship), and every collection has per-document and per-role permissions.

    From the project:

  • Go to Databases -> Create database.
  • Name it main and click Create.
  • Inside the database, click Create collection.
  • Name it posts and click Create.
  • Add attributes to the collection:

  • Open the Attributes tab -> Create attribute.
  • Choose String, name it title, size 255, mark as Required.
  • Add another: String, content, size 65535, required.
  • Add: String, authorId, size 50, required.
  • Add: DateTime, publishedAt, optional.
  • Add: Enum, status, elements draft,published,archived, required, default draft.
  • Add an index so queries are fast:

  • Open the Indexes tab -> Create index.
  • Name it idx_status_published, type Key, attributes status ASC, publishedAt DESC.
  • Click Create.
  • Set permissions:

  • Open the Settings tab on the collection.
  • Under Permissions, grant:
  • - Read -> any (for a public blog) or users (for logged-in only) - Create -> users - Update, Delete -> leave empty and use per-document permissions
  • Save.
  • You now have a production-ready document collection. Compare this to Firestore, where schema is implicit and indexes must be defined in a firestore.indexes.json file you can't see in the console — Appwrite shows everything upfront.

    For a SQL-first approach with an identical BaaS feature set, see our How to Install Supabase on Ubuntu guide.

    Step 10: Configure Authentication Providers

    Appwrite ships with 30+ built-in auth providers. By default, email/password and anonymous login are enabled. Add social login and magic links from the Auth section.

    Email and Password

    Already on by default. Tweak settings at Auth -> Security:

    • Password history — prevent users from reusing their last N passwords.
    • Password dictionary — block the top 10,000 breached passwords.
    • Session length — defaults to 1 year; shorten for high-security apps.
    • Session limit — cap concurrent sessions per user.

    OAuth 2 Providers

    Under Auth -> Settings -> OAuth2 providers, you can enable any of:

    • Google, Apple, Microsoft, GitHub, GitLab, Bitbucket
    • Discord, Slack, Facebook, Twitch, Spotify
    • Amazon, Auth0, Okta, LinkedIn, Zoom
    • Plus 15+ more
    For Google OAuth:

  • Create OAuth credentials at console.cloud.google.com.
  • Add https://appwrite.yourdomain.com/v1/account/sessions/oauth2/callback/google/[PROJECT_ID] to Authorized redirect URIs.
  • Copy the client ID and secret into the Appwrite provider settings.
  • Toggle Enabled and save.
  • Magic URL and OTP

    Enable under Auth -> Settings:

    • Magic URL login — sends a one-time login link via email.
    • Email OTP — sends a 6-digit code instead of a link.
    • Phone OTP — sends SMS codes via Twilio, MSG91, or Vonage (configure the SMS provider at Messaging -> Providers).

    Multi-Factor Authentication

    Turn on Auth -> Security -> MFA to let end users enroll a TOTP authenticator app in addition to their primary login method.

    All of these work out of the box through the client SDKs without any extra code beyond calling the relevant method (account.createEmailPasswordSession, account.createOAuth2Session, etc.).

    Step 11: Create Storage Buckets

    Appwrite Storage is an S3-compatible file API with built-in image transformation, antivirus scanning, and encryption at rest.

  • Go to Storage -> Create bucket.
  • Name it uploads.
  • Configure:
  • - Maximum file size — 30 MB default, up to _APP_STORAGE_LIMIT in your .env. - Allowed file extensions — leave empty for any, or restrict (jpg,jpeg,png,webp,pdf). - Compression — gzip for text-heavy files, none for already-compressed media. - Encryption — on by default, encrypts files with the _APP_OPENSSL_KEY_V1 secret. - Antivirus — scans uploaded files with ClamAV (enable _APP_ANTIVIRUS=enabled in .env first).
  • Set permissions:
  • - Create -> users (logged-in users can upload) - Read -> any (public bucket) or users (private bucket)
  • Click Create.
  • Every uploaded file gets a unique ID and can be fetched at:

    text
    https://appwrite.yourdomain.com/v1/storage/buckets/uploads/files/FILE_ID/view?project=PROJECT_ID

    For images, Appwrite exposes a preview endpoint with on-the-fly transformations:

    text
    https://appwrite.yourdomain.com/v1/storage/buckets/uploads/files/FILE_ID/preview?width=400&height=400&gravity=center&quality=80&output=webp&project=PROJECT_ID

    This means no more wiring up Sharp or ImageMagick in your app.

    Using S3 or External Storage

    To store files on S3, DigitalOcean Spaces, Backblaze B2, or Wasabi instead of local disk, set in .env:

    bash
    _APP_STORAGE_DEVICE=s3
    _APP_STORAGE_S3_ACCESS_KEY=AKIA...
    _APP_STORAGE_S3_SECRET=...
    _APP_STORAGE_S3_REGION=us-east-1
    _APP_STORAGE_S3_BUCKET=my-appwrite-bucket

    Restart: docker compose up -d.

    Step 12: Deploy Your First Function

    Appwrite Functions run your code in response to HTTP requests, CRON schedules, or platform events (user signup, document update, file upload, etc.). Each function runs in a sandboxed container using OpenRuntimes.

    Supported Runtimes

    As of Appwrite 1.6, OpenRuntimes supports:

    • Node.js 16, 18, 19, 20, 21, 22
    • Python 3.8 through 3.12
    • PHP 8.0, 8.1, 8.2, 8.3
    • Ruby 3.0, 3.1, 3.2, 3.3
    • Deno 1.35, 1.40, 1.46
    • Dart 2.15 through 3.3
    • Bun 1.0, 1.1
    • Go 1.23
    • Java 8, 11, 17, 18, 21
    • Kotlin 1.6 through 1.9
    • Swift 5.5 through 5.10
    • .NET 6.0, 7.0, 8.0
    • C++ 17, 20

    Create a Function

  • Go to Functions -> Create function.
  • Choose Node.js 22 as the runtime.
  • Name it hello-world.
  • Leave the entrypoint as src/main.js.
  • Click Create.
  • Deploy via the CLI

    Install the Appwrite CLI on your laptop:

    bash
    npm install -g appwrite-cli

    Log in and initialize a project:

    bash
    appwrite login
    appwrite init project

    When prompted, choose your self-hosted endpoint https://appwrite.yourdomain.com/v1 and your project ID.

    Pull the function scaffold:

    bash
    appwrite init function

    Pick hello-world. This creates ./functions/hello-world/src/main.js:

    javascript
    export default async ({ req, res, log, error }) => {
      log(Received ${req.method} request to ${req.path});

    if (req.method === 'GET') { return res.json({ message: 'Hello from Appwrite Functions!', timestamp: new Date().toISOString(), }); }

    return res.json({ error: 'Method not allowed' }, 405); };

    Deploy:

    bash
    appwrite push function

    The CLI uploads your code, Appwrite builds it in a container, and within 30-60 seconds the function is live. Test it:

    bash
    curl https://appwrite.yourdomain.com/v1/functions/FUNCTION_ID/executions \
      -H "X-Appwrite-Project: PROJECT_ID" \
      -H "X-Appwrite-Key: YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"async": false, "path": "/", "method": "GET"}'

    Event Triggers and Schedules

    In the function's Settings tab:

    • Events — attach triggers like users..create (runs when any user is created) or databases.main.collections.posts.documents..create (runs on new posts).
    • Schedule — CRON expression (0 /6 for every 6 hours).
    • Timeout — up to _APP_FUNCTIONS_TIMEOUT seconds (default 900).
    • Execute access — who can call the function (any, users, specific user IDs, specific teams).
    For a single-binary, SQLite-based alternative that's even simpler for small apps, check out How to Install PocketBase on Ubuntu.

    Step 13: Configure Webhooks

    Webhooks let external services react to Appwrite events — trigger a Slack alert on signup, POST to a Stripe customer endpoint on user creation, or kick off a Zapier flow on new documents.

  • Go to Project Settings -> Webhooks -> Create webhook.
  • Name it, for example, slack-signup-notifier.
  • Enter the POST URL (for example, https://hooks.slack.com/services/...).
  • Select events:
  • - users.*.create — fires on every new user. - users..sessions..create — fires on every login. - databases.main.collections.posts.documents.*.create — fires on new posts.
  • Enable Signatures — Appwrite will include an X-Appwrite-Webhook-Signature HMAC header so you can verify authenticity.
  • Click Create and copy the signing secret.
  • Every matching event will POST a JSON payload to your URL with the full resource object. Appwrite retries failed webhooks with exponential backoff up to 3 attempts.

    Verifying Signatures (Node.js)

    javascript
    import crypto from 'crypto';

    function verifyAppwriteSignature(signingSecret, url, payload, signature) { const computed = crypto .createHmac('sha1', signingSecret) .update(url + JSON.stringify(payload)) .digest('base64'); return crypto.timingSafeEqual( Buffer.from(computed), Buffer.from(signature) ); }

    Step 14: Integrate an SDK

    With the backend running, wire up your frontend. Here are the most common SDKs.

    Web (JavaScript/TypeScript)

    bash
    npm install appwrite
    javascript
    import { Client, Account, Databases, Storage, ID } from 'appwrite';

    const client = new Client() .setEndpoint('https://appwrite.yourdomain.com/v1') .setProject('PROJECT_ID');

    const account = new Account(client); const databases = new Databases(client); const storage = new Storage(client);

    // Sign up await account.create(ID.unique(), '[email protected]', 'securePassword123', 'Jane Doe');

    // Log in await account.createEmailPasswordSession('[email protected]', 'securePassword123');

    // Create a document await databases.createDocument( 'main', 'posts', ID.unique(), { title: 'Hello Appwrite', content: 'My first post!', authorId: (await account.get()).$id, status: 'published', publishedAt: new Date().toISOString(), } );

    // Upload a file const file = document.getElementById('fileInput').files[0]; await storage.createFile('uploads', ID.unique(), file);

    Node.js (Server)

    bash
    npm install node-appwrite
    javascript
    import { Client, Databases, Users } from 'node-appwrite';

    const client = new Client() .setEndpoint('https://appwrite.yourdomain.com/v1') .setProject('PROJECT_ID') .setKey('YOUR_SERVER_API_KEY');

    const databases = new Databases(client); const users = new Users(client);

    const allUsers = await users.list(); console.log(Total users: ${allUsers.total});

    Flutter

    yaml
    dependencies:
      appwrite: ^13.0.0
    dart
    final client = Client()
      .setEndpoint('https://appwrite.yourdomain.com/v1')
      .setProject('PROJECT_ID');

    final account = Account(client); await account.createEmailPasswordSession( email: '[email protected]', password: 'securePassword123', );

    Realtime Subscriptions

    Every SDK supports realtime updates over WebSockets. Subscribe to any event stream:

    javascript
    client.subscribe('databases.main.collections.posts.documents', (response) => {
      console.log('New event:', response.events, response.payload);
    });

    Post-Install: Backups and Upgrades

    Daily Automated Backup

    Create a backup script at /usr/local/bin/appwrite-backup.sh:

    bash
    #!/bin/bash
    set -e
    BACKUP_DIR=/var/backups/appwrite
    TIMESTAMP=$(date +%Y%m%d-%H%M%S)
    mkdir -p "$BACKUP_DIR"

    Dump MariaDB

    docker exec appwrite-mariadb \ sh -c 'exec mysqldump --all-databases -uroot -p"$MARIADB_ROOT_PASSWORD"' \ | gzip > "$BACKUP_DIR/mariadb-$TIMESTAMP.sql.gz"

    Archive uploads and function builds

    tar czf "$BACKUP_DIR/volumes-$TIMESTAMP.tar.gz" \ /var/lib/docker/volumes/appwrite_appwrite-uploads \ /var/lib/docker/volumes/appwrite_appwrite-functions \ /var/lib/docker/volumes/appwrite_appwrite-builds

    Retain 14 days

    find "$BACKUP_DIR" -type f -mtime +14 -delete
    bash
    sudo chmod +x /usr/local/bin/appwrite-backup.sh
    sudo crontab -e

    Add:

    text
    0 3   * /usr/local/bin/appwrite-backup.sh >> /var/log/appwrite-backup.log 2>&1

    Upgrading Appwrite

    Appwrite follows semantic versioning. To upgrade from the current minor release to the next:

    bash
    cd /root/appwrite
    docker compose down
    docker run -it --rm \
        --volume /var/run/docker.sock:/var/run/docker.sock \
        --volume "$(pwd)"/:/usr/src/code/appwrite:rw \
        --entrypoint="upgrade" \
        appwrite/appwrite:1.7.0
    docker compose up -d

    The upgrade script preserves your .env and runs any schema migrations. Always back up MariaDB before a major version bump.

    Troubleshooting

    ProblemCauseSolution
    Certificate is self-signed / NET::ERR_CERT_AUTHORITY_INVALIDLet's Encrypt HTTP-01 challenge failedCheck DNS resolves to your server: dig +short appwrite.yourdomain.com. Check docker logs appwrite-traefik for ACME errors. Ensure ports 80 and 443 are open at both the VPS firewall and provider.
    Console loads but can't create account_APP_CONSOLE_WHITELIST_EMAILS doesn't include your addressEdit .env, add your email, run docker compose up -d.
    Password reset emails never arriveSMTP not configured or credentials wrongSet _APP_SMTP_* variables. Test with docker exec appwrite sh -c 'curl -v smtp://$_APP_SMTP_HOST:$_APP_SMTP_PORT'.
    Function build failed: connection timeoutExecutor can't reach the internet for package installCheck that Docker has DNS configured: add "dns": ["1.1.1.1","8.8.8.8"] to /etc/docker/daemon.json, then sudo systemctl restart docker.
    502 Bad Gateway on API callsMain appwrite container crasheddocker logs appwrite to see the PHP error, usually an invalid .env value. Fix and docker compose up -d.
    Realtime subscriptions disconnect instantlyReverse proxy in front stripping WebSocket headersIf you run Cloudflare or another proxy, enable WebSocket support. Direct Traefik handles this automatically.
    Disk quota exceeded on uploadsAppwrite volumes filled the diskdf -h; clean old function builds with Functions -> each function -> Deployments -> delete old, or mount a larger volume.
    Can't SSH after firewall setupUFW blocked your own IPLog in via the VPS provider's web console, run sudo ufw allow from YOUR.IP.ADDRESS.HERE.

    Viewing Logs

    Stream a container's logs live:

    bash
    docker compose logs -f appwrite
    docker compose logs -f appwrite-realtime
    docker compose logs -f traefik

    Tail last 100 lines across all services:

    bash
    docker compose logs --tail=100

    FAQ

    How much RAM does Appwrite really need?

    The minimum to run is about 2 GB, but that will feel sluggish once you deploy a couple of functions. For a production setup running 5-10 functions with a few thousand active users, 8 GB is comfortable and 12 GB (our CloudCore Professional spec) gives you room to add a staging environment, a separate Redis, or host your app containers on the same box. MariaDB is the memory hog — tune its innodb_buffer_pool_size down if you need to fit in 4 GB.

    Can I run Appwrite behind Cloudflare?

    Yes, with one caveat: WebSocket subscriptions require Cloudflare's WebSocket support (on by default on paid plans, enabled via Network toggle on free). Use the Flexible or Full (strict) SSL mode — Full (strict) is recommended since Appwrite already has a valid Let's Encrypt cert from Traefik. If you use HTTP-01 challenges, set the DNS record to DNS only (grey cloud) during the first cert issuance, then you can proxy it. For wildcard certificates, switch to Traefik's DNS-01 challenge with a Cloudflare API token.

    How does Appwrite compare to Supabase and PocketBase?

    Appwrite is document-database-first (MariaDB under the hood, but exposed as collections), comes with 30+ OAuth providers, realtime, functions in 14+ languages, and a polished admin console. Best for: apps that want Firebase's feel with full self-hosting and a broad runtime story.

    Supabase is PostgreSQL-first — you write real SQL, use row-level security, and can talk to the database directly. Edge Functions run on Deno only. Best for: teams that want raw SQL power and relational data.

    PocketBase is a single Go binary with SQLite, weighing in at ~20 MB, with auth, a realtime API, and a nice admin UI. Best for: small apps, internal tools, and embedded scenarios where you don't want Docker at all.

    If you want SQL and relational joins, pick Supabase. If you want the smallest footprint, pick PocketBase. If you want the broadest feature set out of the box, Appwrite wins.

    Is Appwrite production-ready for real apps?

    Yes. Appwrite is used in production by companies including Apple (internally), German railway operators, Y Combinator-backed startups, and thousands of indie SaaS teams. The 1.x release line has been stable for over two years, with quarterly minor releases and near-weekly patch releases. For five- and six-figure user counts, a single VPS with 8-12 GB RAM handles it comfortably. Above that, you move MariaDB and Redis to dedicated nodes and scale the API servers horizontally.

    How do I migrate from Firebase to Appwrite?

    Appwrite provides official migration tooling for Firebase, Supabase, and NHost. From the console, go to Project Settings -> Migrations -> Create migration. Paste your Firebase service account JSON, select which resources to import (auth users, Firestore collections, Storage files), and Appwrite will pull them into matching Appwrite primitives. Functions are not auto-migrated — you'll need to rewrite Cloud Functions as Appwrite Functions, but the surface area is similar enough that it's usually a few hours of work per function.

    Do I need a separate queue system like RabbitMQ or BullMQ?

    No. Appwrite uses Redis internally for all asynchronous workloads — function invocations, webhook deliveries, outbound emails, and event fan-out all go through the built-in queue. You get retries, backoff, and dead-letter handling for free. Only add an external queue if you have app-level background jobs that don't map to Appwrite functions or webhooks.

    Next Steps

    You now have a production Appwrite instance with TLS, a custom domain, auth, databases, storage, functions, and webhooks. Here's what to build next:

    • Set up monitoring with Uptime Kuma — point it at https://appwrite.yourdomain.com/v1/health and at each of your function HTTP endpoints for sub-minute outage alerts.
    • Add object storage offsite — switch _APP_STORAGE_DEVICE to s3 with a provider like Backblaze B2 for cheap durable storage and automatic geo-redundancy.
    • Wire up CI/CD for functions — GitHub Actions + the Appwrite CLI deploys functions on every merge to main. Commit appwrite.json to your repo so your schema is versioned.
    • Enable per-tenant custom domains — Appwrite supports user-attached domains, perfect for SaaS with white-label URLs. Configure _APP_DOMAIN_TARGET and point customer CNAMEs at your instance.
    • Compare alternatives — read our Supabase install guide or PocketBase install guide to see which BaaS fits your stack best.
    • Read the official self-hosting docs — the Appwrite self-hosting reference covers scaling, Kubernetes, and enterprise features in depth.

    Skip the Manual Install — Get a Pre-Tuned Backend VPS
    >
    Our CloudCore Professional plan gives you 6 vCPU, 12 GB RAM, and 100 GB NVMe — the sweet spot for running Appwrite comfortably alongside your app. Deploy in 60 seconds with Docker and Traefik already installed.
    >
    - Docker Engine + Compose pre-installed
    - UFW firewall pre-configured for 80/443
    - Let's Encrypt tooling ready
    - systemd, log rotation, and automatic security updates enabled
    - EUR 19.99/month, unmetered bandwidth
    >
    Deploy Your Backend VPS Now

    Was this article helpful?

    ← Back to Install GuidesBrowse all categories →

    Still have questions?

    Contact Support →Submit a Ticket