Skip to content

How to Deploy Appwrite on a VPS

Updated Sep 2026

verified on Ubuntu 26.04 · Sep 2026
We earn commissions when you shop through the links below. Full disclosure →

Self-host Appwrite, the open-source backend-as-a-service, on a VPS using its official installer run unattended. Covers domain and certificates, locking down the console, backups and upgrades.

Before you start
  • A VPS with at least 2 vCPU / 4 GB RAM (the catalog minimum; Appwrite runs around thirty containers)
  • A fresh Ubuntu 24.04 or 26.04 server with root/sudo SSH access
  • A domain you can point at the server, with ports 80 and 443 free for Appwrite's own proxy
  • Docker Engine + Compose installed (see the base guide below)
Need a box for this guide? Kamatera's free tier lets you spin one up now.Start free on Kamatera → (opens in new tab)

What Appwrite is

Appwrite is a BSD-3-Clause backend-as-a-service. It bundles authentication, databases, file storage, serverless functions, messaging, real-time subscriptions and static site hosting behind one API and a web console, with SDKs for web, mobile and server. Supabase and Firebase fill the same role. Supabase vs Appwrite and PocketBase vs Appwrite compare them, and Directus vs Appwrite covers the data-platform angle.

Appwrite is not a single container. The official installer writes a Docker Compose project with the API, the console, MongoDB, Redis, a Traefik proxy, an executor for functions, and a long list of workers and schedulers. On our test box it started 29 containers. Size the server for that and treat it as a dedicated machine.

Server sizing

The catalog's minimum for Appwrite is 4 GB RAM, with 2 vCPU. We don't have a formal idle measurement for it yet. As a rough check, shortly after install on our 8 GB test box (GCP e2-standard-2, Ubuntu 26.04), about 3.5 GB of the machine's memory was in use, OS included.

  • 4 GB RAM / 2 vCPU: the floor. Fine for development and small projects.
  • 8 GB RAM / 4 vCPU: comfortable. Function builds and executions run in extra containers and are the first thing to run out of memory.
  • Disk: 40 GB or more. Images, the database, uploaded files and function and site builds all accumulate.

Give Appwrite ports 80 and 443 to itself. Its Traefik container binds them, obtains Let's Encrypt certificates for your domain, and routes custom domains for functions and sites. Running another reverse proxy on the same ports means reworking that setup, which this guide doesn't cover.

Prepare the server

This guide assumes Docker Engine and the Compose plugin are installed. If not, work through Docker & Compose on Ubuntu first. Open SSH and the two web ports:

sudo ufw allow OpenSSH
sudo ufw allow 80
sudo ufw allow 443
sudo ufw --force enable
sudo ufw status verbose

Point an A record for appwrite.example.com at the server's public IP now, so DNS has time to propagate before you configure the domain.

Where to host itaffiliate disclosure
Hetzner Cloudrun it on
2 vCPU · 4 GB RAM · 80 GB SSD · $23.59/mo
Get Hetzner Cloud (opens in new tab)
Kamaterafree trial
1 vCPU · 1 GB RAM · 20 GB SSD · $4.00/mo
Start free on Kamatera → (opens in new tab)
DigitalOceanalso works on
1 vCPU · 1 GB RAM · 25 GB SSD · $6.00/mo
Deploy on DigitalOcean → (opens in new tab)

Paid link — we earn a commission if you shop through it.

Install Appwrite (official installer, unattended)

Appwrite installs through its own image. The container gets the Docker socket, writes docker-compose.yml and .env into ./appwrite, generates the secrets and starts the stack. By default it opens an interactive web installer. Passing --interactive=N together with the ports skips that and uses the defaults, which is easier to repeat:

cd ~
docker run --rm \
  --volume /var/run/docker.sock:/var/run/docker.sock \
  --volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
  --entrypoint="install" \
  appwrite/appwrite:1.9.5 \
  --interactive=N --http-port=80 --https-port=443

The first run pulls a lot of images and took about three minutes on our test box. When it finishes, check that the stack is up. The main appwrite container and MongoDB report health checks:

cd ~/appwrite
docker compose ps --format '{{.Name}}\t{{.Status}}'

Some scheduler and task containers restarted once during the first boot in our test and then stayed up. That is normal. Look for containers that keep restarting, not ones that restarted once.

The console answers on port 80 and redirects to /console/:

curl -s -o /dev/null -w '%{http_code} -> %{redirect_url}\n' http://localhost/

Everything the installer generated lives in ~/appwrite/.env: the database passwords, _APP_OPENSSL_KEY_V1 (the key Appwrite encrypts data with) and the executor secret. This file matters as much as the database. Without it, a restored database is unreadable. The installer runs as root, so the files are owned by root. Use sudo to edit them.

Domain and HTTPS

A fresh install thinks its domain is localhost. Point it at your real hostname by editing the domain variables in .env. _APP_DOMAIN is the console and API hostname, the _APP_DOMAIN_TARGET_* values are what custom domains should point to, and _APP_SYSTEM_SECURITY_EMAIL_ADDRESS is the contact address used for Let's Encrypt:

cd ~/appwrite
sudo sed -i \
  -e 's/^_APP_DOMAIN=.*/_APP_DOMAIN="appwrite.example.com"/' \
  -e 's/^_APP_DOMAIN_TARGET=.*/_APP_DOMAIN_TARGET="appwrite.example.com"/' \
  -e 's/^_APP_DOMAIN_TARGET_CNAME=.*/_APP_DOMAIN_TARGET_CNAME="appwrite.example.com"/' \
  -e 's/^_APP_DOMAIN_TARGET_A=.*/_APP_DOMAIN_TARGET_A="203.0.113.10"/' \
  -e 's/^_APP_SYSTEM_SECURITY_EMAIL_ADDRESS=.*/_APP_SYSTEM_SECURITY_EMAIL_ADDRESS="you@example.com"/' \
  .env
docker compose up -d

Use your server's real IPv4 address for _APP_DOMAIN_TARGET_A. Set _APP_DOMAIN_FUNCTIONS and _APP_DOMAIN_SITES too if you plan to serve functions or sites from their own subdomains. Their defaults are functions.localhost and sites.localhost.

Once https://appwrite.example.com/console/ loads with a valid certificate, turn on HTTPS redirects. Set _APP_OPTIONS_FORCE_HTTPS and _APP_OPTIONS_ROUTER_FORCE_HTTPS from disabled to enabled in .env, then run docker compose up -d again. Do this after the certificate works. Forcing HTTPS first locks you out of a console that can't yet serve it.

Securing the console

Open the console and create your account immediately. The installer sets _APP_CONSOLE_WHITELIST_ROOT="enabled", which lets only the first user sign up to the console. It is the instance's root account, so claim it before anyone else finds the URL. After that:

  • Restrict console sign-ups further if you need to, with _APP_CONSOLE_WHITELIST_EMAILS (allowed addresses) or _APP_CONSOLE_WHITELIST_IPS. Both are empty by default.
  • Leave abuse protection on. _APP_OPTIONS_ABUSE is enabled by default and rate-limits the API.
  • Configure SMTP (_APP_SMTP_HOST and related variables). Otherwise email verification, password recovery and team invitations won't send.
  • Treat API keys like passwords. Create one per server-side integration with only the scopes it needs. Never put a server API key in a client app. Client SDKs authenticate as users, not with keys.
  • Protect ~/appwrite/.env. Back it up somewhere encrypted and keep it out of git.

Backups

Appwrite's state has three parts: the MongoDB database, the named volumes (uploads, functions, builds, sites, certificates, config), and .env. The installer names the volumes after the project, for example appwrite_appwrite-uploads. Check with docker volume ls.

A database dump with the root credentials from .env:

cd ~/appwrite
mkdir -p ~/appwrite-backups
set -a; . ./.env; set +a
docker compose exec -T mongodb mongodump -u root -p "$_APP_DB_ROOT_PASS" \
  --authenticationDatabase admin --archive --gzip > ~/appwrite-backups/mongo-$(date +%F).archive.gz
ls -lh ~/appwrite-backups/

Then the file volumes and the configuration:

cd ~/appwrite
for v in uploads functions builds sites certificates config; do
  docker run --rm -v appwrite_appwrite-$v:/data:ro -v ~/appwrite-backups:/backup alpine \
    tar czf /backup/$v-$(date +%F).tar.gz -C /data .
done
sudo cp .env docker-compose.yml ~/appwrite-backups/
sudo chown -R "$USER": ~/appwrite-backups
ls -lh ~/appwrite-backups/

Copy ~/appwrite-backups off the server. To restore, reinstall the same Appwrite version, put back .env and the volumes, and load the archive with mongorestore --archive --gzip inside the mongodb container.

Upgrades

Upstream's upgrade path is the installer again, run with the new version from the directory that contains appwrite/. It detects the existing install and keeps your .env. After that, run the data migration inside the new container. Back up first, every time. Read the release notes too, because Appwrite versions sometimes need specific upgrade steps.

cd ~
docker run --rm \
  --volume /var/run/docker.sock:/var/run/docker.sock \
  --volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
  --entrypoint="upgrade" \
  appwrite/appwrite:<new-version> --interactive=N
cd ~/appwrite
docker compose exec appwrite migrate

Don't try to roll back by changing tags. The migration changes your data, so the way back is the backup you took first.

Troubleshooting

The installer finishes but the console never loads. Run docker compose ps in ~/appwrite and read the logs of anything not running, starting with docker compose logs appwrite --tail 100 and docker compose logs mongodb --tail 100. On a 4 GB box, memory is the usual cause. Check free -m.

No certificate for your domain. DNS isn't pointing at the server yet, port 80 is blocked, or _APP_DOMAIN is still localhost. Traefik obtains certificates for the configured domain only. Read docker compose logs traefik and docker compose logs appwrite-worker-certificates.

"Sign up is restricted" on the console. The root whitelist is doing its job: someone, probably you, already created the first account. Sign in with it, or adjust the whitelist variables.

Functions fail to build or run. Builds and executions run through the executor with the Docker socket. Check docker compose logs openruntimes-executor, and check free memory and disk. Both run out quickly on the smallest plans.

Verification + next steps

You're done when:

  • https://appwrite.example.com/console/ loads over a valid certificate;
  • you have created the root account;
  • the HTTPS-redirect options are enabled;
  • a test project with one collection and one file survives docker compose restart;
  • a dated MongoDB dump, the volume archives and .env exist off the server.

From there, connect a client SDK to a project, or deploy a first function. For a lighter backend, see PocketBase on a VPS. For a Postgres-based one, see Supabase on a VPS. For ranked hosts, see Best VPS for self-hosting.

Next steps

How to self-host Appwrite →More self-hosted backend & baas tools →Best VPS for PocketBase →Automatic HTTPS with Caddy →Run Claude Code with Ollama on Your Own VPS →Deploy Coolify on a VPS →How to Deploy Actual Budget on a VPS →How to Deploy AnythingLLM on a VPS →How to Deploy Audiobookshelf on a VPS →How to Deploy Authelia on a VPS →How to Deploy authentik on a VPS →How to Deploy Baserow on a VPS →How to Deploy Beszel on a VPS →How to Deploy Bitwarden on a VPS →How to Deploy BookStack on a VPS →How to Deploy CapRover on a VPS →How to Deploy Checkmate on a VPS →How to Deploy Directus on a VPS →How to Deploy docker-mailserver on a VPS →How to Deploy Docmost on a VPS →How to Deploy Dokku on a VPS →How to Deploy Dokploy on a VPS →How to Deploy Firefly III on a VPS →How to Deploy Forgejo on a VPS →How to Deploy Gatus on a VPS →How to Deploy Ghostfolio on a VPS →How to Deploy Gitea on a VPS →How to Deploy GitLab on a VPS →How to Deploy GlitchTip on a VPS →How to Deploy Grafana on a VPS →How to Deploy Graylog on a VPS →How to Deploy Headscale on a VPS →How to Deploy Healthchecks on a VPS →How to Deploy Home Assistant on a VPS →How to Deploy Immich on a VPS →How to Deploy Jan on a VPS →How to Deploy Jellyfin on a VPS →How to Deploy Karakeep on a VPS →How to Deploy Keycloak on a VPS →How to Deploy Leantime on a VPS →How to Deploy LibreChat on a VPS →How to Deploy Linkwarden on a VPS →How to Deploy LocalAI on a VPS →How to Deploy Mailcow on a VPS →How to Deploy Mailu on a VPS →How to Deploy Matomo on a VPS →How to Deploy Mattermost on a VPS →How to Deploy Meilisearch on a VPS →How to Deploy Memos on a VPS →How to Deploy n8n on a VPS →How to Deploy Navidrome on a VPS →How to Deploy NetBird on a VPS →How to Deploy Netdata on a VPS →How to Deploy Nextcloud on a VPS →How to Deploy Next.js to a VPS →How to Deploy Nginx Proxy Manager on a VPS →How to Deploy NocoDB on a VPS →How to Deploy ntfy on a VPS →How to Deploy Ollama on a VPS →How to Deploy Open WebUI on a VPS →How to Deploy OpenHands on a VPS →How to Deploy OpenObserve on a VPS →How to Deploy OpenProject on a VPS →How to Deploy Outline on a VPS →How to Deploy Pangolin on a VPS →How to Deploy Paperless-ngx on a VPS →How to Deploy Passbolt on a VPS →How to Deploy Plane on a VPS →How to Deploy Plausible Analytics on a VPS →How to Deploy Pocket ID on a VPS →How to Deploy PocketBase on a VPS →How to Deploy Prometheus on a VPS →How to Deploy Psono on a VPS →How to Deploy Radarr on a VPS →How to Deploy Rocket.Chat on a VPS →How to Deploy SigNoz on a VPS →How to Deploy Sonarr on a VPS →How to Deploy Stalwart on a VPS →How to Deploy Stirling-PDF on a VPS →How to Deploy Supabase on a VPS →How to Deploy Synapse on a VPS →How to Deploy Taiga on a VPS →How to Deploy TeamPass on a VPS →How to Deploy Tinyauth on a VPS →How to Deploy Traefik on a VPS →How to Deploy Trilium on a VPS →How to Deploy Twenty CRM on a VPS →How to Deploy Umami on a VPS →How to Deploy Uptime Kuma on a VPS →How to Deploy Vaultwarden on a VPS →How to Deploy Vikunja on a VPS →How to Deploy wg-easy on a VPS →How to Deploy Wiki.js on a VPS →How to Deploy Zabbix on a VPS →How to Deploy Zitadel on a VPS →How to Deploy Zulip on a VPS →Docker & Compose on Ubuntu 26.04 →Building AI Workflows with n8n →Install Open WebUI with Ollama →Adding AI-Powered Insights to Plausible Analytics →Building AI-Powered Apps with Supabase and pgvector →

We use analytics cookies (Google Analytics, PostHog) to see which guides are useful. No ad networks, no cross-site tracking. See our privacy policy.