Appwrite is an open-source backend as a service (BaaS) that bundles authentication, databases, file storage, serverless functions, messaging and realtime APIs behind one REST and SDK interface. A self-hosted instance runs as a set of Docker containers with a Traefik proxy that obtains its own Let's Encrypt certificate. In this tutorial you will install Appwrite on Ubuntu 24.04 with the official installer, serve it at https://appwrite.your_domain, restrict who can create console accounts, configure SMTP, test the API and set up database backups.

Prerequisites

To follow this tutorial you need:

  • A server running Ubuntu 24.04 LTS with at least 2 vCPUs and 4 GB of RAM, for example a CubePath VPS.
  • A non-root user with sudo privileges.
  • Docker Engine and the Docker Compose plugin installed from Docker's official repository.
  • A domain name with an A record for appwrite.your_domain pointing to your_server_ip.
  • Ports 80 and 443 free on the server. Appwrite's Traefik proxy listens on both, so do not run Nginx or Apache on the same host.
  • Optionally, an SMTP account so Appwrite can send verification, password recovery and invitation emails.

Step 1 - Opening the firewall

Allow SSH, HTTP and HTTPS. Port 80 is also needed for the Let's Encrypt HTTP challenge:

sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable

Verify the rules:

sudo ufw status
Status: active

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

Also confirm that the DNS record already resolves to your server, since the certificate is requested on first access:

dig +short appwrite.your_domain
your_server_ip

Step 2 - Running the Appwrite installer

Appwrite ships its installer inside the appwrite/appwrite image. It asks a few questions and writes a docker-compose.yml and .env into an appwrite directory under the current directory. Always install a specific version. Look up the latest release:

APPWRITE_VERSION=$(curl -fsSL https://api.github.com/repos/appwrite/appwrite/releases/latest | grep -oP '"tag_name": "\K[^"]+')
echo "$APPWRITE_VERSION"
1.x.y

Create a directory for the installation and run the installer from it:

sudo mkdir -p /opt/appwrite-install
cd /opt/appwrite-install
sudo 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:${APPWRITE_VERSION}"

Answer the prompts:

  • HTTP port: 80
  • HTTPS port: 443
  • Secret API key: accept the generated value. It becomes _APP_OPENSSL_KEY_V1 and encrypts sensitive data, so store a copy somewhere safe. Losing it makes encrypted data unreadable.
  • Appwrite hostname: appwrite.your_domain
  • DNS A record hostname: appwrite.your_domain. This is the target your users point custom domains to.

The installer pulls the images and starts the stack. When it finishes, check the containers:

cd /opt/appwrite-install/appwrite
sudo docker compose ps --format 'table {{.Service}}\t{{.Status}}' | head -n 8
SERVICE                 STATUS
appwrite                Up 1 minute
appwrite-realtime       Up 1 minute
mariadb                 Up 1 minute
redis                   Up 1 minute
traefik                 Up 1 minute
...

Appwrite runs more than 20 containers, most of them background workers. All of them should be Up.

Step 3 - Securing the configuration

The generated .env holds the database passwords and the encryption key. Restrict it to root:

sudo chmod 600 /opt/appwrite-install/appwrite/.env

Open the file to adjust a few settings:

sudo nano /opt/appwrite-install/appwrite/.env

Set the email used for Let's Encrypt registration, and make sure only the first user can create a console account. Without this, anyone who finds your instance can sign up and create projects:

_APP_SYSTEM_SECURITY_EMAIL_ADDRESS=admin@your_domain
_APP_CONSOLE_WHITELIST_ROOT=enabled
_APP_OPTIONS_FORCE_HTTPS=enabled

_APP_CONSOLE_WHITELIST_ROOT=enabled allows a single root console account. To allow specific colleagues later, list their addresses in _APP_CONSOLE_WHITELIST_EMAILS, separated by commas.

If you have an SMTP account, fill in the mail settings in the same file:

_APP_SMTP_HOST=smtp.your_provider.com
_APP_SMTP_PORT=587
_APP_SMTP_SECURE=tls
_APP_SMTP_USERNAME=your_smtp_user
_APP_SMTP_PASSWORD=your_smtp_password
_APP_SYSTEM_EMAIL_ADDRESS=noreply@your_domain
_APP_SYSTEM_EMAIL_NAME=Your App

Apply the changes. docker compose up -d recreates every container whose configuration changed; a plain restart would not reload .env:

cd /opt/appwrite-install/appwrite
sudo docker compose up -d

Step 4 - Creating the console account

Open https://appwrite.your_domain in your browser. On the first HTTPS request, Appwrite's certificates worker requests a Let's Encrypt certificate, so the first load can show a certificate warning for a few seconds. Reload once it is issued.

Sign up with your email and a strong password. This first account is the root console account. Then create a project, for example My App, and note its Project ID from the project settings.

Check the certificate and the API from your workstation. The health endpoint returns the running version without authentication:

curl -s https://appwrite.your_domain/v1/health/version
{"version":"1.x.y"}

Step 5 - Testing the API

Your apps talk to Appwrite through its REST API or the SDKs. Test the client API by creating a user in your project. Replace your_project_id with the ID from the previous step:

curl -s -X POST "https://appwrite.your_domain/v1/account" \
  -H "Content-Type: application/json" \
  -H "X-Appwrite-Project: your_project_id" \
  -d '{"userId": "unique()", "email": "[email protected]", "password": "your_strong_password", "name": "Test User"}'

The response is the new user as JSON, including its $id. Open Auth in the console to see the user listed.

To call Appwrite from a browser app, first add a Web platform with the app's hostname in the project's Overview page. Appwrite rejects browser requests from origins that are not registered. Then install the Web SDK in your frontend:

npm install appwrite

And create a session for the user:

import { Client, Account } from "appwrite";

const client = new Client()
  .setEndpoint("https://appwrite.your_domain/v1")
  .setProject("your_project_id");

const account = new Account(client);

const session = await account.createEmailPasswordSession(
  "[email protected]",
  "your_strong_password"
);
console.log(session.userId);

Server-side code uses an API key instead. Create one in the project console, grant only the scopes the service needs, and keep it out of browser code.

Step 6 - Backing up Appwrite

Appwrite keeps its data in MariaDB and in Docker volumes for uploads, function builds and certificates. Dump the database from inside the mariadb container:

cd /opt/appwrite-install/appwrite
sudo docker compose exec -T mariadb sh -c 'exec mysqldump --all-databases --add-drop-database --single-transaction -uroot -p"$MYSQL_ROOT_PASSWORD"' > appwrite-$(date +%F).sql

Check the dump:

ls -lh appwrite-*.sql

List the volumes that hold files, which you should also copy off the server:

sudo docker volume ls --format '{{.Name}}' | grep appwrite
appwrite_appwrite-builds
appwrite_appwrite-certificates
appwrite_appwrite-functions
appwrite_appwrite-uploads
...

Keep a copy of .env with every backup. Without the original _APP_OPENSSL_KEY_V1, a restored database cannot decrypt its secrets.

Step 7 - Upgrading Appwrite

Take a backup first. Then run the installer of the new version from the same parent directory. It detects the existing installation and updates docker-compose.yml while keeping your .env:

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

After the containers are up, run the data migration:

cd /opt/appwrite-install/appwrite
sudo docker compose exec appwrite migrate

Read the release notes before every upgrade and move one minor version at a time instead of jumping several releases.

Troubleshooting

The console shows a certificate error for a long time. The DNS record must resolve to this server and port 80 must be reachable from the internet. Check the certificates worker:

sudo docker compose logs appwrite-worker-certificates --tail 50

The console does not load or returns 500. Check the main API container:

sudo docker compose logs appwrite --tail 100

Emails are never received. Verify the SMTP values in .env, apply them with sudo docker compose up -d and check the mails worker with sudo docker compose logs appwrite-worker-mails --tail 50.

Browser requests fail with CORS errors. The origin of your frontend is not registered as a Web platform in the project. Add it as a Web platform in the project.

Conclusion

You installed Appwrite on Ubuntu 24.04, served it over HTTPS on your own domain, restricted console sign-ups, tested the client API and prepared backups and upgrades. Next, create databases and collections from the console, add OAuth providers under Auth, Settings, or deploy your first function from a Git repository in the Functions section.