Skip to content
Go back

Appwrite: Your Own Firebase, Minus the Google Surveillance Subscription

By SumGuy 8 min read
Appwrite: Your Own Firebase, Minus the Google Surveillance Subscription

Firebase Is Great Until It Isn’t

Firebase solves the “I just want to ship this app without building a backend” problem elegantly. Authentication, a real-time database, file storage, cloud functions, all with client SDKs that just work. For prototypes and side projects, it’s phenomenal.

Then one of three things happens:

  1. Your app gets traffic and the bill arrives
  2. Google announces another product sunset (Firebase has been on the rumored list more than once)
  3. Your data is in a proprietary format in Google’s infrastructure and you realize you have no exit strategy

Appwrite is the self-hosted answer. All the same capabilities, auth, databases, storage, functions, real-time, running on your own hardware. Your data stays yours. No cold start bills. No platform risk.

What BaaS Is and Why Self-Hosting It Makes Sense

Backend-as-a-Service gives your frontend app a complete backend without writing server-side code. Instead of spinning up API routes, a database, and an auth system, you call an SDK:

// Log in a user — no backend code written by you
const session = await account.createEmailPasswordSession(email, password);
// Store some data — no API endpoint written by you
const doc = await databases.createDocument(DB_ID, COLLECTION_ID, ID.unique(), {
title: "My note",
content: "BaaS is convenient"
});

Why self-host instead of using the managed cloud version?

The tradeoff: you’re responsible for uptime, updates, and backups. For a side project or internal tool, that’s a fair trade.

Appwrite’s Feature Set

Before diving into setup, here’s what you’re actually getting:

For a typical CRUD app or mobile app backend, this covers everything.

Docker Compose Deployment

Appwrite uses Docker and comes with a one-command installer:

Terminal window
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.8.1

The installer prompts for:

It generates a complete docker-compose.yml with all the required services. The full stack includes:

# What the generated docker-compose.yml runs:
services:
appwrite: # Main API server
appwrite-worker-*: # Multiple worker containers for background tasks
mariadb: # Primary database
redis: # Caching and pub/sub
telegraf: # Metrics collection
influxdb: # Metrics storage
traefik: # Reverse proxy (built-in)

Yes, it’s a lot of containers. Appwrite is not a lightweight stack, this is a full-featured backend platform. Minimum realistic requirements: 2 CPU cores, 4GB RAM. Runs fine on a $20/month VPS or a decent home server.

Terminal window
# Start Appwrite
cd appwrite
docker compose up -d
# Check everything is running
docker compose ps
# Appwrite console: http://your-hostname or https://your-hostname

Creating a Project via the Console

  1. Open the Appwrite console at your hostname
  2. Sign up for the first account (becomes the owner)
  3. Create Project → Enter a name
  4. Note the Project ID: you’ll need it in your SDK code

Create a database and collection:

  1. DatabasesCreate Database → Note the Database ID
  2. Create Collection → Configure attributes (schema):
String attribute: "title" — required, max 255 chars
String attribute: "content" — required, max 5000 chars
Boolean attribute: "published" — optional, default false
DateTime attribute: "created_at" — required
  1. Indexes → Add an index on created_at for efficient sorting

Configure permissions on the collection:

Roles → Create:
- Any: can create (for open registration apps)
- Users: can read own documents
- Admins: full access

Appwrite’s permission system uses roles like any, users, user:{id}, team:{id}, and label:{name}. This is document-level, every document can have individual permissions.

JavaScript SDK Quickstart

Terminal window
npm install appwrite
import { Client, Account, Databases, ID } from "appwrite";
// Initialize client
const client = new Client()
.setEndpoint("https://your-appwrite-instance.com/v1")
.setProject("your-project-id");
const account = new Account(client);
const databases = new Databases(client);
const DB_ID = "your-database-id";
const COLLECTION_ID = "your-collection-id";
// Create an account
async function register(email, password, name) {
return await account.create(ID.unique(), email, password, name);
}
// Log in
async function login(email, password) {
return await account.createEmailPasswordSession(email, password);
}
// Create a document
async function createNote(title, content) {
return await databases.createDocument(
DB_ID,
COLLECTION_ID,
ID.unique(), // auto-generate ID
{
title,
content,
published: false,
created_at: new Date().toISOString()
}
);
}
// Query documents
async function getNotes() {
const { documents } = await databases.listDocuments(
DB_ID,
COLLECTION_ID,
[
Query.equal("published", true),
Query.orderDesc("created_at"),
Query.limit(25)
]
);
return documents;
}
// Real-time subscription
const unsubscribe = client.subscribe(
`databases.${DB_ID}.collections.${COLLECTION_ID}.documents`,
(response) => {
if (response.events.includes("databases.*.collections.*.documents.*.create")) {
console.log("New document:", response.payload);
}
}
);
// Unsubscribe when done
// unsubscribe();

File Storage

import { Storage, InputFile } from "appwrite";
const storage = new Storage(client);
const BUCKET_ID = "your-bucket-id";
// Upload a file
async function uploadFile(file) {
return await storage.createFile(
BUCKET_ID,
ID.unique(),
InputFile.fromBlob(file, file.name)
);
}
// Get a file preview URL (with image transformations)
function getImageUrl(fileId, width = 400) {
return storage.getFilePreview(BUCKET_ID, fileId, width);
}
// Direct download URL
function getDownloadUrl(fileId) {
return storage.getFileDownload(BUCKET_ID, fileId);
}

Buckets have configurable permissions, max file size, and allowed MIME types. You can restrict a bucket to only accept image/jpeg and image/png with a 5MB max, built into the bucket settings, no validation code required.

Appwrite Functions

Serverless functions run on the same instance. Trigger them from the console, via HTTP, or on Appwrite events.

functions/send-welcome-email/src/main.js
import { Client, Messaging } from "node-appwrite";
export default async ({ req, res, log, error }) => {
const client = new Client()
.setEndpoint(process.env.APPWRITE_FUNCTION_API_ENDPOINT)
.setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID)
.setKey(req.headers["x-appwrite-key"]);
// Triggered on new user registration
const userId = req.body.userId;
log(`Sending welcome email to user ${userId}`);
// Your email logic here
return res.json({ success: true });
};

Deploy via CLI:

Terminal window
npm install -g appwrite-cli
appwrite login
appwrite deploy function --functionId your-function-id

Functions are isolated per runtime, with configurable CPU/memory limits. Cold starts are non-zero but acceptable for webhook-style use cases.

Appwrite vs Supabase vs PocketBase

FeatureAppwriteSupabasePocketBase
Database typeDocument (JSON)PostgreSQLSQLite
Resource usageHigh (multi-container)HighVery low (single binary)
Auth providers30+20+Standard
RealtimeWebSocket eventsPostgreSQL LISTENSSE
FunctionsBuilt-inEdge Functions (Deno)JS hooks only
Setup complexityMediumMediumVery low
Best forFull-featured appsSQL power usersLightweight projects

Choose Appwrite for mobile apps or frontend-heavy apps where you want a full feature set and don’t need PostgreSQL specifically.

Choose Supabase if you want SQL, need complex queries, or are building something that grows into a real database-backed application.

Choose PocketBase if you want the absolute minimum, single binary, SQLite, runs on a $5 VPS, perfect for side projects where Appwrite’s multi-container overhead is overkill.

Resource Requirements and Upgrading

Minimum: 2 vCPU, 4GB RAM, 20GB storage Recommended for production: 4 vCPU, 8GB RAM, SSD storage

Upgrading Appwrite:

Terminal window
cd appwrite
# Check current version
docker compose exec appwrite app --version
# Pull new images
docker compose pull
# Run migrations
docker compose run --rm appwrite migrate
# Restart
docker compose up -d
# Verify
docker compose ps

Always back up the MariaDB database before upgrading:

Terminal window
docker compose exec mariadb sh -c \
"exec mysqldump -u appwrite -p"$MARIADB_ROOT_PASSWORD" appwrite" \
> appwrite-backup-$(date +%Y%m%d).sql

Ideal Use Cases

Appwrite is genuinely well-suited for:

It’s overkill for:

For its sweet spot, “I need auth, file storage, and a database for my app without writing server code”, Appwrite is solid and the self-hosted version is genuinely production-ready.


Share this post on:

Send a Webmention

Written about this post on your own site? Send a webmention and it'll show up above once verified.


Previous Post
Stable Diffusion vs ComfyUI vs Fooocus: AI Image Generation at Home
Next Post
Linux Suspend and Hibernate: Teaching Your Machine to Take a Nap Without Dying

Discussion

Powered by Garrul . Sign in with GitHub or Google, or post anonymously.

Related Posts