DockerSelf-HostingAI Coding Agents
AFFiNE with Docker: a self-hosted Notion and Miro alternative (Linux, Windows, Mac)
Before you start
- 20 min read
Boxes marked Run in terminal are what you type; plain text explains why. Check what you need first

What you get in one minute#
AFFiNE is an open-source workspace that merges documents, a free-form whiteboard and databases in one app. You can run its server yourself with Docker Compose and keep your notes on your own machine.

Logo: the official AFFiNE project (affine.pro)
The project calls AFFiNE "a privacy-focused, local-first, open-source, and ready-to-use alternative for Notion & Miro". It suits a person or small team that wants wiki pages, boards and a free-form canvas without a cloud account. Know three things first: the project says it is in beta, the server code has its own licence (see "The licence, honestly"), and a self-hosted workspace gets at most 10 seats.
| What | Value (official docs, read November 2026) |
|---|---|
| Current version | AFFiNE 0.27.4, released August 2026 (latest stable on GitHub) |
| Image to pin | ghcr.io/toeverything/affine:0.27.4; the tag stable pointed at the same image when we checked |
| Time needed | About 20 minutes for the Compose route (our estimate, not measured) |
| RAM and disk | Docs: 2 GB RAM for basic use, 4 GB for large docs, 4 CPU cores recommended, about 1.5 GB disk for the server |
| Port | TCP 3010 (the web app, the API and sync) |
| Licence | Mostly MIT; the server (packages/backend) has its own licence, read it below |
| Official docs | docs.affine.pro/self-host-affine and the repository |
At a glance:
- What runs: the AFFiNE server, a one-time migration job, Postgres (with pgvector) and Redis.
- Where data lives: Postgres holds users and documents; uploads go to a folder; settings go in
config/config.json. - Configuration changed in 0.27: settings now live in
config/config.jsonand the Admin Panel, not in.env. Old tutorials are wrong for this. - Seats: up to 10 per workspace; more needs a Team Plan or an Installable License.
- AI: bring your own key (BYOK) from OpenAI, Anthropic, Gemini or FAL.
- Upgrades are the risky part: 0.27.0 and 0.27.4 broke some upgrades. Back up first and pin the version.
How it fits together#
Your browser or app talks to one AFFiNE server container. That server uses Postgres for the data, Redis for cache, background jobs and sync, and a folder for uploaded files. An HTTPS proxy in front is optional on one machine and a must on a server.
Drawn for this guide from the official Compose file and docs. The dashed HTTPS route is optional.
Before you start#
You need Docker with Compose v2. The docs treat Docker Compose as the supported route and name no operating-system limits. The images are built for Linux on amd64, arm64 and arm/v7.
| System | What to use | Notes |
|---|---|---|
| Linux | Docker Engine with the Compose plugin | The best choice for a server others use |
| Windows | Docker Desktop (Linux containers) | For trying it on your own PC; untested by us |
| Mac | Docker Desktop | Same; Apple silicon uses the arm64 image |
Check Docker on every system:
docker --version docker compose version
Checklist:
- [ ] Docker and Docker Compose v2 installed (the check above works)
- [ ] 2 GB RAM free (4 GB for long documents) and about 1.5 GB of disk
- [ ] Port 3010 free on the machine (
ss -ltn | grep 3010on Linux shows nothing) - [ ] A backup plan (see "Updates and backups" below)
- [ ] For the HTTPS section only: a domain name with a DNS record pointing at the server, and ports 80 and 443 free
Tested by DIY Lab Hub: not yet. The commands come from the official Compose file, config example and docs read in November 2026. We did not run them.
Step 1: Create the folders#
We keep everything under one folder, as all DIY Lab Hub guides do. The official Compose file expects ./data/postgres, ./data/storage and ./config next to it.
Linux#
mkdir -p ~/docker_volumes/affine/{data,config,backups}
cd ~/docker_volumes/affine
Windows (PowerShell)#
New-Item -ItemType Directory -Force "$HOME\docker_volumes\affine\data", "$HOME\docker_volumes\affine\config", "$HOME\docker_volumes\affine\backups" | Out-Null Set-Location "$HOME\docker_volumes\affine"
Mac (Terminal)#
Same commands as Linux:
mkdir -p ~/docker_volumes/affine/{data,config,backups}
cd ~/docker_volumes/affine
Check it worked. ls (Linux, Mac) or dir (Windows) shows the folders data, config and backups.
Tip: The docs ask you to fix the data and config locations before the first start and not to move them once the instance holds data.
Step 2: Write the Compose file#
This is the official docker-compose.yml from the v0.27.4 release with three changes of ours: AFFiNE is pinned to 0.27.4 (the official file says stable), Redis to redis:7 (the docs call Redis 6.x or 7.x "fully tested"; the official file leaves it unpinned), and the web port is published on 127.0.0.1 only (the official file uses every interface).
Linux#
cd ~/docker_volumes/affine
cat > docker-compose.yml << 'EOF'
name: affine
services:
affine:
image: ghcr.io/toeverything/affine:0.27.4
container_name: affine_server
ports:
- '127.0.0.1:3010:3010'
depends_on:
redis:
condition: service_healthy
postgres:
condition: service_healthy
affine_migration:
condition: service_completed_successfully
volumes:
- ./data/storage:/root/.affine/storage
- ./config:/root/.affine/config
environment:
- REDIS_SERVER_HOST=redis
- DATABASE_URL=postgresql://affine@postgres:5432/affine
- AFFINE_INDEXER_ENABLED=false
restart: unless-stopped
affine_migration:
image: ghcr.io/toeverything/affine:0.27.4
container_name: affine_migration_job
volumes:
- ./data/storage:/root/.affine/storage
- ./config:/root/.affine/config
command: ['sh', '-c', 'node ./scripts/self-host-predeploy.js']
environment:
- REDIS_SERVER_HOST=redis
- DATABASE_URL=postgresql://affine@postgres:5432/affine
- AFFINE_INDEXER_ENABLED=false
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
redis:
image: redis:7
container_name: affine_redis
healthcheck:
test: ['CMD', 'redis-cli', '--raw', 'incr', 'ping']
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
postgres:
image: pgvector/pgvector:pg16
container_name: affine_postgres
volumes:
- ./data/postgres:/var/lib/postgresql/data
environment:
POSTGRES_USER: affine
POSTGRES_DB: affine
POSTGRES_INITDB_ARGS: '--data-checksums'
POSTGRES_HOST_AUTH_METHOD: trust
healthcheck:
test: ['CMD', 'pg_isready', '-U', 'affine', '-d', 'affine']
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
EOF
Windows (PowerShell)#
Open the file in Notepad, paste the same YAML as in the Linux block, and save:
notepad docker-compose.yml
Mac (Terminal)#
Same command as Linux: run the cat > docker-compose.yml << 'EOF' block above in ~/docker_volumes/affine.
Check it worked. Ask Compose to read the file; it prints the merged configuration and no error:
docker compose config --quiet && echo "compose file OK"
On Windows, docker compose config --quiet; if ($?) { "compose file OK" } does the same.
Tip: The official file runs Postgres with
POSTGRES_HOST_AUTH_METHOD: trust, which means no password. That is safe only because Postgres publishes no port. Never add aports:line to thepostgresservice.
Step 3: Write the configuration file#
Since 0.27 the server reads config/config.json. The one setting you must get right is server.externalUrl: it is the exact address people type into the browser.
Linux#
cd ~/docker_volumes/affine
cat > config/config.json << 'EOF'
{
"$schema": "https://github.com/toeverything/affine/releases/latest/download/config.schema.json",
"server": {
"name": "AFFiNE Self-hosted",
"externalUrl": "http://localhost:3010"
},
"copilot": {
"enabled": true,
"byok": {
"enabled": true
}
}
}
EOF
chmod 600 config/config.json
Windows (PowerShell)#
notepad config\config.json
Paste the same JSON as in the Linux block and save.
Mac (Terminal)#
Same commands as Linux.
Check it worked. The file is valid JSON. On Linux or Mac with Python installed: python3 -m json.tool config/config.json. On Windows: Get-Content config\config.json -Raw | ConvertFrom-Json | Out-Null. No output means it parsed.
Tip:
localhost:3010works only if you open exactlyhttp://localhost:3010.http://127.0.0.1:3010or a LAN address gets blocked, because the server checks the browser's origin againstexternalUrl(issue #15552, open).
Step 4: Start AFFiNE#
Linux#
cd ~/docker_volumes/affine docker compose up -d docker compose ps -a docker compose logs --tail=200 affine affine_migration
Windows (PowerShell)#
Set-Location "$HOME\docker_volumes\affine" docker compose up -d docker compose ps -a docker compose logs --tail=200 affine affine_migration
Mac (Terminal)#
Same commands as Linux.
Check it worked. The docs' expected result: the one-time migration container finishes successfully, while AFFiNE, Postgres and Redis keep running. In docker compose ps -a that means affine_migration_job shows Exited (0) and the other three show running or healthy. The first start downloads the images and runs migrations, so give it a few minutes. The docs publish no expected log text; read the log for errors.
Tip: If
affine_serverdoes not start, read theaffine_migration_joblog first; the server waits for it.
Step 5: Create the admin account#
Linux#
Open http://localhost:3010 in a browser on the same machine. The docs say the first visit sends you to the admin sign-up page (/admin), where you register the administrator and log in. On a remote server, use an SSH tunnel and then open the same address:
ssh -N -L 3010:127.0.0.1:3010 <your-user>@<your-server-address>
Windows (PowerShell)#
Open http://localhost:3010 in your browser. For a remote server, run the same tunnel in PowerShell and keep the window open:
ssh -N -L 3010:127.0.0.1:3010 <your-user>@<your-server-address>
Mac (Terminal)#
Same as Linux, including the ssh -N -L tunnel command.
Optional: install the desktop app from the download page, add your server in the workspace list at the top left, and log in. Server 0.27 needs clients 0.26 or newer; clients 0.27 need servers 0.27 (release notes).
Check it worked. You can log in and create a doc. http://localhost:3010/admin/settings opens the Admin Panel where server settings can be edited.
Tip: Store the admin password in a password manager now. If the admin sign-up page ever appears again, you are looking at an empty database (see Troubleshooting).
Put it behind HTTPS (with or without Traefik)#
Plain http://localhost:3010 is fine on your own computer. For other people, put HTTPS in front on a name such as https://affine.<your-domain>. This section is for a server with a domain; local-only users can skip it. Both routes need a DNS record pointing at the server. AFFiNE sync uses websockets and the docs say your proxy must allow them; Traefik and Caddy do by default.
After either route, change server.externalUrl in config/config.json to the final HTTPS address and run docker compose up -d to recreate the containers.
With Traefik#
On a server that already runs Traefik v3 with the Docker provider, add labels to the affine service and drop its host port.
This assumes an external Docker network proxy and an entrypoint https; use your own names. Create the network once if missing:
docker network create proxy
Change the affine service: remove ports:, add the proxy network next to default (Postgres and Redis stay on it), and add the labels.
services:
affine:
image: ghcr.io/toeverything/affine:0.27.4
networks:
- default
- proxy
labels:
- "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.routers.affine.rule=Host(`affine.<your-domain>`)"
- "traefik.http.routers.affine.entrypoints=https"
- "traefik.http.routers.affine.tls=true"
- "traefik.http.services.affine.loadbalancer.server.port=3010"
# keep depends_on, volumes, environment and restart from Step 2
networks:
proxy:
external: true
Then set externalUrl to https://affine.<your-domain> and run docker compose up -d.
Check it worked. curl -I https://affine.<your-domain>/ returns an HTTP status line over HTTPS, and the page opens with a valid certificate.
Without Traefik: Caddy#
Choose this if you have no Traefik. Caddy gets and renews its own certificates.
Caddy gets Let's Encrypt certificates for a public domain and makes its own local certificate (tls internal) for names that exist only at home. Run it as one more service in the same Compose file, mounting the conf folder, not a single Caddyfile (Caddy's Docker docs warn against single-file mounts). Remove the ports: block of affine: Caddy reaches it by name on the Compose network.
Create the folder and the Caddyfile:
mkdir -p ~/docker_volumes/affine/caddy nano ~/docker_volumes/affine/caddy/Caddyfile
On Windows: New-Item -ItemType Directory -Force "$HOME\docker_volumes\affine\caddy" and notepad "$HOME\docker_volumes\affine\caddy\Caddyfile". For a public domain, the file is:
affine.example.com {
reverse_proxy affine:3010
}
For a home-only name, use tls internal instead:
affine.home.arpa {
tls internal
reverse_proxy affine:3010
}
Add this service and these volumes to docker-compose.yml. The image caddy:2.11.8 was the latest Caddy tag on Docker Hub when we checked:
caddy:
image: caddy:2.11.8
container_name: affine_caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- ./caddy:/etc/caddy
- caddy_data:/data
- caddy_config:/config
depends_on:
- affine
volumes:
caddy_data:
caddy_config:
The caddy service goes under services:; volumes: is top level. Then set externalUrl and run docker compose up -d.
For the home-only name, each device must trust Caddy's own root certificate. Copy it out of the container (run this on the machine that runs Docker), then import it on each device. These import commands follow Caddy's docs for each system:
docker compose cp caddy:/data/caddy/pki/authorities/local/root.crt ./root.crt
- Linux:
sudo cp root.crt /usr/local/share/ca-certificates/root.crt && sudo update-ca-certificates - Windows (PowerShell, as Administrator):
certutil -addstore -f "ROOT" root.crt - Mac (Terminal):
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain root.crt
Caddy's docs add that Firefox and Chrome may need root.crt imported by hand. Point the name at the server's LAN address in your router's DNS or a hosts file.
Check it worked. curl -I https://affine.example.com/ returns an HTTP status line over HTTPS. For a home-only name, the browser shows a padlock only after the root is trusted on that device. See also our Caddy reverse proxy guide.
Tip: A certificate error usually means the name in the Caddyfile is not the name in DNS, or ports 80 and 443 do not reach the server.
What self-hosting changes#
The cloud version at app.affine.pro and your own server run the same app; the surroundings differ (official self-host docs).
| Topic | Self-hosted (your server) |
|---|---|
| Seats | Up to 10 per workspace; more needs a Team Plan, or an Installable License for servers that cannot reach the licence service |
| File storage | Unlimited quota and size per the docs; local folder by default, or S3-compatible storage or Cloudflare R2 |
| AI | BYOK only: each workspace owner adds a provider key under Settings, Integrations, AI BYOK |
| Meeting notes (macOS) | The docs page says self-hosted is not supported; it may be older than the BYOK docs |
| Login | Email codes need SMTP (Admin Panel, Notification); Google, GitHub and OIDC are under Admin Panel, OAuth |
| Who does the work | You: Postgres, Redis, backups, upgrades and HTTPS |
The licence, honestly#
The repository's LICENSE says most content is MIT, but everything under packages/backend and packages/common/native uses the licence at packages/backend/server/LICENSE: the "AFFiNE Enterprise Edition (EE) license". Its text says the software may be used in production only if you have agreed to the AFFiNE Subscription Terms of Service or another agreement and have a valid Enterprise Edition subscription for the right number of seats. You may modify it, publish patches and use it for development and testing, but not copy, publish or sell it. Another paragraph puts parts distributed as the Community Edition, or served client-side, under MPL 2.0 and does not list which parts those are.
The self-host docs give every workspace up to 10 seats and sell larger teams a Team Plan. We cannot say how the two fit for your case, and we are not lawyers. For a personal server the docs' 10 seats are the clear route; for a company, read both files and ask the AFFiNE team first.
Security and testing#
Postgres runs without a password, so what protects your data is the network around it.
| Control | How |
|---|---|
| Postgres and Redis | Publish no port for them. Only the affine container reaches them on the Compose network |
| Web port | 127.0.0.1:3010 until HTTPS works; then no host port at all |
| Admin account | A long unique password; the Admin Panel is at /admin |
| Sign-up | The docs mention people registering themselves; check Admin Panel, Settings before exposing the server |
config/config.json | chmod 600; keep it readable by trusted admins only (SMTP credentials) |
config/private.key | Created by the pre-deploy step; keep it backed up and private, since replacing it can invalidate signed data (docs) |
| Backups | They hold every document and the key file; treat them as secret |
Quick tests after each change (on Windows use curl.exe):
cd ~/docker_volumes/affine docker compose ps -a docker compose logs --tail=100 affine curl -I http://localhost:3010/
What leaves the machine: if you use AI, the text goes to the provider whose key you added; a Team licence is checked against app.affine.pro. The docs list optional Google Analytics settings but describe no default telemetry.
Common mistakes#
From the issue tracker (read November 2026), the docs' warnings and common AI answers.
| Mistake | Why it happens | Do this instead |
|---|---|---|
Swapping in the new Compose file on an old .env install | 0.27.4 moved Postgres to ./data/postgres, uploads to ./data/storage, and logs in as a passwordless affine role. Issue #15517 (closed): users and workspaces vanished and then role "affine" does not exist | Follow the docs' legacy .env checklist: back up, diff the files, keep the old paths or restore into the new layout. Never delete .env or old folders before it works |
| Upgrading without a backup | Issue #15240 (closed): 0.26.7 to 0.27.0 left tables missing and Prisma stuck on P3009 for some users; a fixed 0.27 tag followed | Back up Postgres and uploads first, pin the tag, read the release notes, read the affine_migration_job log |
Opening AFFiNE at a different address than externalUrl | The CORS allow-list is built from server.externalUrl. Issue #15552 (open): every request from another origin is blocked | Use one address and put it in externalUrl; recreate the containers after a change |
Putting AI provider keys in config.json or .env | Old tutorials did this. The docs now say keys belong to the workspace | Add keys under Settings, Integrations, AI BYOK. Issue #15294 (closed) got the same answer |
| Mixing client and server versions | The 0.27 release notes: clients 0.27 need servers 0.27, servers 0.27 need clients 0.26 or newer | Upgrade server and apps together |
| Expecting the mobile apps to just work | 0.27 reports (iOS #15343, closed; Android #15299, closed as a duplicate) describe self-hosted sign-in failing | Test web and desktop first; check the tracker before promising mobile access |
| Jumping to a beta tag | Issue #15784 (open): a 0.27.5 beta failed migration with column "auth_epoch" does not exist | Stay on the stable tag |
AI assistants: telling you to put DB_PASSWORD, UPLOAD_LOCATION or AFFINE_REVISION in a .env file | These were the old Compose variables. The docs say the current Compose file no longer reads them | Use the official file, config/config.json and the Admin Panel |
AI assistants: recommending stable or latest as the image | Many examples copy the official file; the docs say not to use latest as a blind upgrade path | Pin 0.27.4 and change the tag yourself after reading the release notes |
Tip: Before you copy any Compose file from a blog or an AI answer, compare its image tag, volume paths and database line with the official file at the release tag you pin.
Tip: Run
docker compose logs --tail=200 affine affine_migrationafter every start or upgrade. Most failures print there.
Tip: Test your backup the day you set it up, not the day you need it. Reading the dump with
pg_restore --listtakes seconds.
Troubleshooting#
| Problem | Solution |
|---|---|
| The browser asks you to create a new administrator after an upgrade | The new Compose file started an empty Postgres folder. Stop it, go back to the old files, take a backup, then migrate on purpose (docs, Upgrade) |
FATAL: role "affine" does not exist | The database was created with another user (old DB_USERNAME). Use the old Compose file to dump with the real role; do not create an affine role to silence it |
Migration job fails or tables are missing (relation "mail_deliveries" does not exist, Prisma P3009) | Seen on 0.27.0 (#15240). Read the migration log; the reported fix was prisma migrate resolve --applied 20260618000000_backend_runtime_tables inside the container, plus the corrected 0.27 image |
Blocked CORS request from origin: ... in the server log, buttons say "Connection failed" | server.externalUrl does not match the address in the browser. Fix it and run docker compose up -d |
| "Failed to generate invitation link" on a new server | A commenter on #15372 (closed) found a log line saying new accounts cannot create invite links for 24 hours. This is a user report, not in the official docs. Wait a day, or invite by email with SMTP set |
| Sync icon spins forever | Check the proxy passes websockets, then check client and server versions match the rule above |
| Logged out repeatedly on a desktop app | Reported on 0.27.x clients (#15654, #15590, both closed); update the client |
| Port 3010 already in use | Change the left side of the mapping, for example 127.0.0.1:3011:3010, and use that address in externalUrl |
Resetting a user's password#
Admin Panel, Accounts, the user's operations menu, "Reset Password": send the link to the user. The docs warn that Delete and Ban remove a user's data for good.
Known bugs (November 2026)#
- #15784 (open): migration to 0.27.5-beta.2 fails. Use the stable tag.
- #15552 (open): BYOK failures traced to
externalUrland probe settings.
Diagnose with docker compose ps -a, docker compose logs --tail=200 affine affine_migration and docker inspect affine_server.
Updates and backups#
Update. The docs' route is to download the target release's Compose file next to the old one, compare, then swap. For a pinned file like ours, change the tag in docker-compose.yml (both affine and affine_migration), after the backup below:
cd ~/docker_volumes/affine
AFFINE_VERSION=v0.xx.x # replace with the target release tag
curl -L -o docker-compose.yml.new "https://github.com/toeverything/affine/releases/download/${AFFINE_VERSION}/docker-compose.yml"
diff -u docker-compose.yml docker-compose.yml.new
Carry your changes into the new file, replace the old one, then run docker compose pull, docker compose up -d and the log command. The affine_migration service migrates the database on start. Then check login, workspaces, editing and uploads.
Back up. A complete backup is Postgres, the uploaded files and the configuration. The Postgres command is the docs' own; it reads the real user and database name from the container.
Linux#
cd ~/docker_volumes/affine mkdir -p backups STAMP=$(date +%F) docker exec affine_postgres sh -c 'pg_dump --format=custom --file=/tmp/affine.backup --username="$POSTGRES_USER" "$POSTGRES_DB"' docker cp affine_postgres:/tmp/affine.backup "backups/affine-db-$STAMP.backup" docker exec affine_postgres sh -c 'pg_restore --list /tmp/affine.backup > /dev/null' && echo "dump is readable" tar -czf "backups/affine-files-$STAMP.tar.gz" data/storage config docker-compose.yml chmod 600 backups/*
To run it every night at 03:00, save those lines as ~/docker_volumes/affine/backup.sh, run chmod +x backup.sh, and add 0 3 * * * $HOME/docker_volumes/affine/backup.sh with crontab -e. Copy the files in backups to another machine.
Windows (PowerShell)#
The $ characters are escaped so PowerShell does not expand them (our adaptation of the docs' command):
Set-Location "$HOME\docker_volumes\affine" New-Item -ItemType Directory -Force backups | Out-Null $stamp = Get-Date -Format yyyy-MM-dd docker exec affine_postgres sh -c "pg_dump --format=custom --file=/tmp/affine.backup --username=`$POSTGRES_USER `$POSTGRES_DB" docker cp affine_postgres:/tmp/affine.backup "backups\affine-db-$stamp.backup" docker exec affine_postgres sh -c "pg_restore --list /tmp/affine.backup > /dev/null" tar -czf "backups\affine-files-$stamp.tar.gz" data/storage config docker-compose.yml
To schedule it, save those lines as backup.ps1 and run schtasks /Create /SC DAILY /ST 03:00 /TN "AFFiNE backup" /TR "powershell -NoProfile -File $HOME\docker_volumes\affine\backup.ps1".
Mac (Terminal)#
Same script as Linux. Schedule it with crontab -e (grant cron Full Disk Access if macOS blocks it) or a launchd job.
Restore. The docs' rule: stop AFFiNE, never restore over your only copy, and restore into a new, empty ./data/postgres folder while keeping the old one. Start only Postgres, copy the dump in, run pg_restore --format=custom --clean --if-exists --no-owner --no-privileges with the container's own user and database, then start everything so the migration job runs. A full restore test needs a spare machine or the live stack stopped; at least run the pg_restore --list check above after every backup.
Postgres major upgrades (16 to 17) are a backup and restore, not a tag change; the docs say so plainly.
Performance and resources#
The docs give these figures, from what the project sees on its own cloud:
- RAM: 2 GB for basic use; 4 GB if some documents pass 10,000 words (merging a document with 10,000 edits peaks near 1 GB).
- CPU: at least 4 cores recommended.
- Disk: about 1.5 GB for the server, 100 MB of Postgres per 1,000 docs of 1,000 words, 10 GB of uploads per 1,000 files.
- Prometheus: on by default, about 200 MB.
- Image sizes (amd64, compressed, registry data): AFFiNE about 228 MB,
pgvector/pgvector:pg16about 156 MB,redis:7about 44 MB.
We did not measure a running instance; run docker stats for your own numbers. Larger setups can use an external Postgres (DATABASE_URL), an external Redis (REDIS_SERVER_HOST) or S3-compatible file storage.
FAQ#
Is AFFiNE free to self-host? The app and the server image cost nothing, and up to 10 seats per workspace are included. The server code has its own licence; read the licence section above.
Is the project dead, archived or renamed? No. The repository is active, with a release in August 2026 and beta tags after it. The project itself says it is in beta.
Can I use my own Postgres or Redis? Yes. Set DATABASE_URL (Postgres is the only supported database) or the REDIS_SERVER_* variables, and remove the bundled service. Redis 6 or 7 is the tested range.
How does it compare with Outline or BookStack? Those are wiki tools; AFFiNE adds a whiteboard and databases. If you only need a wiki, see our Outline guide and BookStack guide.
Sources#
- AFFiNE release v0.27.4, README, docker-compose.yml, config.json.example, LICENSE and server LICENSE - read November 2026
- AFFiNE self-host docs: overview, requirements, Docker Compose, configuration, upgrade, backup and restore, domain and HTTPS, Postgres, AI, seats, Team License, environment variables and legacy .env deployments - read November 2026
- Version and releases, Meeting (macOS) - read November 2026
- AFFiNE image on GitHub Container Registry (tag
0.27.4checked) and Docker Hub tags for redis, pgvector and caddy - November 2026 - Caddy: running with Docker Compose and automatic HTTPS - read November 2026
- GitHub issues #15240, #15294, #15299, #15343, #15372, #15517, #15527, #15552, #15590, #15654 and #15784 - read November 2026
Checked against the official docs on 2026-11-05.