How to Self-Host Appwrite on a VPS: Backend as a Service with Auth, Database, and Storage
Appwrite is an open-source Backend as a Service (BaaS) that provides authentication (30+ OAuth providers), a document database with real-time subscriptions, file storage, serverless functions, push notifications, and team management — all in one Docker Compose stack. It competes directly with Firebase and Supabase. Unlike Supabase (PostgreSQL-based), Appwrite uses its own document database and MariaDB internally, making it simpler to self-host on smaller VPS instances.
Appwrite vs Supabase vs Pocketbase
| Factor | Appwrite | Supabase | Pocketbase |
|---|---|---|---|
| Database | Document DB (NoSQL-like) | PostgreSQL (SQL) | SQLite |
| Min RAM | 2 GB | 4 GB | 32 MB |
| Functions | Yes (30+ runtimes) | Yes (Deno/Postgres) | JS hooks only |
| Realtime | Yes (all resources) | Yes (PostgreSQL) | Yes (SSE) |
| Auth providers | 30+ OAuth | 20+ OAuth | OAuth2 + custom |
| Setup complexity | Medium | High | Very low |
Server Requirements
- Minimum 2 GB RAM (3–4 GB recommended for production)
- Docker and Docker Compose
- 20+ GB NVMe storage
Step 1: Install Appwrite
<code">mkdir -p /opt/appwrite && cd /opt/appwrite
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:latest
The interactive installer asks:
- HTTP port: 80 (or leave default)
- HTTPS port: 443
- Hostname:
appwrite.yourdomain.com - DNS resolver: default
- Skip SSL: No — provide your own via Nginx
Alternatively, use the manual Docker Compose approach:
<code">curl -o docker-compose.yml https://appwrite.io/install/compose curl -o .env https://appwrite.io/install/env nano .env
<code"># Required changes in .env: _APP_ENV=production _APP_OPENSSL_KEY_V1=your_32_char_key # openssl rand -hex 16 _APP_DOMAIN=appwrite.yourdomain.com _APP_DOMAIN_TARGET=appwrite.yourdomain.com _APP_SMTP_HOST=smtp.mailgun.org _APP_SMTP_PORT=587 _APP_SMTP_SECURE=tls _APP_SMTP_USERNAME=postmaster@mg.yourdomain.com _APP_SMTP_PASSWORD=your_smtp_password _APP_SMTP_FROM=noreply@yourdomain.com
<code">docker compose up -d # Appwrite starts multiple services — wait 2–3 minutes docker compose ps
Step 2: Configure Nginx for SSL
Appwrite runs its own Traefik internally on ports 80 and 443. To put Nginx in front (with your existing Certbot certs):
<code"># First, change Appwrite's Traefik to use non-standard ports # Edit .env: _APP_PORT=8080 _APP_HTTPS_PORT=8443 # Restart docker compose up -d
<code">sudo nano /etc/nginx/sites-available/appwrite
<code">server {
listen 443 ssl http2;
server_name appwrite.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/appwrite.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/appwrite.yourdomain.com/privkey.pem;
client_max_body_size 100M;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 300s;
}
}
<code">sudo certbot --nginx -d appwrite.yourdomain.com sudo ln -s /etc/nginx/sites-available/appwrite /etc/nginx/sites-enabled/ sudo systemctl reload nginx
Step 3: Create First Project
- Visit
https://appwrite.yourdomain.com - Create admin account (first user becomes admin)
- Create Project → name it → add platform (Web, Flutter, Android, iOS)
- Copy the Project ID and API Endpoint
Step 4: Configure Authentication
- Project → Auth → Settings
- Enable desired methods: Email/Password, Magic URL, Google, GitHub, Apple
- For OAuth providers: add Client ID and Secret from respective developer consoles
- Configure allowed redirect URLs for OAuth flows
Step 5: Create a Database Collection
<code"># Via Appwrite SDK — create a collection programmatically
from appwrite.client import Client
from appwrite.services.databases import Databases
from appwrite.input_file import InputFile
from appwrite.id import ID
from appwrite.permission import Permission
from appwrite.role import Role
client = Client()
client.set_endpoint('https://appwrite.yourdomain.com/v1')
client.set_project('your_project_id')
client.set_key('your_api_key')
databases = Databases(client)
# Create database
db = databases.create(ID.unique(), 'Blog')
# Create collection
collection = databases.create_collection(
database_id=db['$id'],
collection_id=ID.unique(),
name='posts',
permissions=[
Permission.read(Role.any()), # Anyone can read
Permission.create(Role.users()), # Only logged-in users can create
]
)
# Add attributes
databases.create_string_attribute(db['$id'], collection['$id'], 'title', 255, required=True)
databases.create_string_attribute(db['$id'], collection['$id'], 'content', 65535, required=True)
databases.create_boolean_attribute(db['$id'], collection['$id'], 'published', required=False)
Step 6: JavaScript SDK Integration
<code">npm install appwrite
<code">// client.js
import { Client, Account, Databases, Storage } from 'appwrite';
const client = new Client()
.setEndpoint('https://appwrite.yourdomain.com/v1')
.setProject('your_project_id');
export const account = new Account(client);
export const databases = new Databases(client);
export const storage = new Storage(client);
// Auth: create account
await account.create(ID.unique(), 'user@example.com', 'password123', 'John Doe');
// Auth: login
const session = await account.createEmailPasswordSession('user@example.com', 'password123');
// Database: create document
const post = await databases.createDocument(
'blog_db_id',
'posts_collection_id',
ID.unique(),
{ title: 'Hello World', content: 'My first post', published: true }
);
// Database: list documents with filter
const posts = await databases.listDocuments('blog_db_id', 'posts_collection_id', [
Query.equal('published', [true]),
Query.orderDesc('$createdAt'),
Query.limit(10),
]);
// Storage: upload file
const fileInput = document.getElementById('file');
const file = await storage.createFile('bucket_id', ID.unique(), fileInput.files[0]);
Appwrite Functions (Serverless)
<code"># Create a function in the Appwrite console
# Choose runtime: Node.js 20, Python 3.12, PHP 8.3, Dart, etc.
# Trigger: HTTP request, event (document.create, etc.), or schedule
# Example Node.js function:
export default async ({ req, res, log }) => {
const { name } = JSON.parse(req.body);
log(`Processing request for: ${name}`);
return res.json({ greeting: `Hello, ${name}!` });
};
Getting Started
Appwrite needs 2 GB RAM minimum; 3–4 GB is comfortable for production with multiple active users. KVM VPS plans at VPS.DO with 4 GB RAM run Appwrite comfortably. NVMe storage benefits file storage performance and MariaDB query speed. Appwrite is an excellent choice for teams building mobile apps (Flutter support is first-class) or web apps that need more than a simple REST API without the SQL complexity of Supabase.
Conclusion
Self-hosted Appwrite provides a complete BaaS stack — authentication with 30+ OAuth providers, a document database with real-time subscriptions, file storage, and serverless functions — on a VPS at infrastructure cost. Its document database model is more accessible than Supabase’s SQL approach for teams without database expertise, while Pocketbase is the lighter-weight single-binary alternative for simpler use cases.