SSD Nodes Learn Hosting plans →
Guias Matt ConnorPor Matt Connor

Como alojar o AFFiNE com Docker Compose

Execute o AFFiNE num VPS com Docker Compose: conheça os quatro contentores, tags fixas, dados, backups e o que 2 GB de RAM realmente suportam.

O que se obtém ao alojar o AFFiNE por conta própria

A alojar o AFFiNE por conta própria, obtém um workspace ao estilo do Notion num servidor sob o seu controlo, executado como quatro contentores: a aplicação, uma tarefa de migração executada uma vez, o Postgres e o Redis. A colaboração em tempo real está incluída, até ao limite predefinido de 10 utilizadores de um workspace autoalojado. A instalação requer um ficheiro compose e um ficheiro de configuração JSON. Os pontos que exigem atenção são as tags das imagens, a disposição do disco, o limite de memória e o proxy colocado à frente da aplicação.

O AFFiNE mantém um editor de documentos e um canvas infinito no mesmo workspace. Assim, uma página pode ser lida como um documento ou expandida como um quadro branco. Se ainda está a decidir o que executar, leia primeiro a comparação de alternativas autoalojadas ao Notion. Este guia parte do princípio de que a escolha já foi feita e explica como executar o AFFiNE corretamente, em vez de voltar a compará-lo.

Tudo o que é apresentado aqui foi verificado na documentação de self-hosting do AFFiNE e nos ficheiros de release publicados em 8 de agosto de 2026. A versão estável mais recente nessa data era a 0.27.3, publicada em 23 de julho de 2026. As afirmações abaixo que têm uma data posterior foram verificadas novamente nas fontes primárias em 12 de outubro de 2026. Nessa altura, a versão estável mais recente era a 0.27.4, publicada em 18 de agosto de 2026.

O que os quatro contentores fazem realmente

affine é o servidor e o cliente web na mesma imagem. Escuta na porta 3010.

affine_migration é uma tarefa executada uma única vez que executa node ./scripts/self-host-predeploy.js, aplica as migrações da base de dados e termina. A aplicação declara condition: service_completed_successfully para essa tarefa, por isso uma migração que termine com um estado diferente de zero significa que affine nunca é iniciado. Quando a interface web não fica disponível, o log dessa tarefa é o primeiro que deve consultar.

postgres armazena os seus documentos, utilizadores, espaços de trabalho e permissões. A imagem fornecida é pgvector/pgvector:pg16, que corresponde a Postgres 16 normal com a extensão pgvector compilada. O pgvector adiciona ao Postgres um tipo de coluna vector, o formato numérico usado para armazenar embeddings, para que o texto possa ser pesquisado pelo significado.

redis é uma dependência obrigatória: tanto o servidor como a tarefa de migração aguardam pela verificação de integridade antes de serem iniciados. Repare no que o ficheiro compose fornecido não atribui ao Redis: um volume. Nada no Redis sobrevive a um docker compose down, o que indica claramente que não armazena conteúdo seu e não precisa de cópia de segurança.

Por que a imagem do Postgres é pgvector e não o postgres padrão

O requisito vem do esquema do AFFiNE, não de uma preferência. Na v0.27.4, schema.prisma ainda declara extensions = [pgvector(map: "vector")], mas não contém colunas de embeddings. Os quatro modelos com colunas embedding do tipo vector(1024) na v0.27.3 não existem na v0.27.4. Mantenha a imagem pgvector/pgvector:pg16 fornecida: a extensão continua declarada, e o postgres:16 padrão não a inclui.

O template oficial da v0.27.4 usa a imagem pgvector; o template da v0.21.0 ainda usava postgres:16. Numa instalação mais antiga, editar a linha da imagem não conclui a atualização. Leia a página de atualização na documentação de self-host do AFFiNE antes de fazer qualquer pull.

Há mais um ponto sobre essa tag. pg16 significa Postgres 16, e uma versão principal do Postgres não é um número que possa ser incrementado. Se a alterar para pg17 sobre um diretório de dados existente, o Postgres recusa-se a iniciar, com uma linha como The data directory was initialized by PostgreSQL version 16, which is not compatible with this version 17 em docker compose logs postgres. A migração entre versões principais requer uma das três opções indicadas nas notas de versão do PostgreSQL 17: fazer um dump e restaurá-lo num diretório de dados novo, usar pg_upgrade ou usar replicação lógica. Numa instalação única com Compose, o dump e o restore costumam ser a opção mais simples, porque pg_upgrade requer os binários do servidor antigo e do novo lado a lado.

Quanta CPU e RAM o AFFiNE autoalojado precisa

A página de requisitos do AFFiNE pede pelo menos 4 núcleos de CPU e 2 GB de RAM. A memória necessária sobe para 4 GB quando os seus documentos ultrapassam 10,000 palavras. A mesma página explica para onde vai a memória: o sistema de sincronização e a fusão de documentos. Também apresenta um valor que vale a pena memorizar: a fusão de um documento com 10,000 modificações pode atingir um pico de 1 GB.

Compare agora estes valores com um plano de 2 GB usado por duas pessoas. A utilização média é suficiente. O Postgres e o processo Node ficam abaixo do limite, com alguma margem. O problema é o pico. Uma única fusão grande pode elevar a utilização de memória do servidor para cerca de 1 GB. Num servidor com 2 GB e sem swap, esse pico, somado ao Postgres e aos restantes processos residentes, pode esgotar a memória. O kernel ativa então o out-of-memory (OOM) killer para libertar memória, terminando um processo. O servidor AFFiNE, por ser o maior processo, é o candidato mais provável. No entanto, a documentação do Docker deixa claro que qualquer processo pode ser escolhido, incluindo o próprio Docker.

Quando é o servidor AFFiNE que termina, o seu colega não vê uma mensagem de erro. A página é recarregada, porque restart: unless-stopped repõe o contentor em execução em poucos segundos. Não faça suposições. Confirme:

docker inspect affine_server --format '{{.State.OOMKilled}} {{.RestartCount}}'
sudo dmesg -T | grep -i -E 'out of memory|killed process'

true na saída do primeiro comando, ou uma linha Killed process que identifique node na saída do segundo, significa que ficou sem memória. Não significa que encontrou um bug. Corrija o problema pelos dois lados. Primeiro, adicione swap, para que um pico tenha espaço temporário para descarregar antes de o OOM killer atuar:

sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
free -h

free -h deve indicar agora um total de 2.0Gi de swap. A swap não torna o AFFiNE rápido e não é esse o objetivo. Ela fornece um destino temporário para um pico curto. Assim, é muito mais provável que o pico cause um segundo de lentidão do que a paragem do contentor. É uma proteção temporária, não uma garantia. O outro lado da correção consiste em impedir que o Postgres faça crescer a sua cache para ocupar o espaço de que a aplicação precisa durante a fusão. É para isso que servem os limites de memória num serviço Compose. Se os processos continuarem a ser terminados depois de aplicar ambas as medidas, o servidor precisa dos 4 GB pedidos na página de requisitos do AFFiNE para documentos maiores.

O armazenamento é muito mais fácil de prever. Estes são os valores publicados pelo AFFiNE na mesma página:

GráficoPublished AFFiNE storage figures, August 2026
Os dados por trás deste gráfico
[
  {
    "label": "Server install",
    "gb": 1.5
  },
  {
    "label": "Postgres per 1,000 docs",
    "gb": 0.1
  },
  {
    "label": "Blob store per 1,000 uploads",
    "gb": 10
  }
]

A instalação do servidor ocupa 1.5 GB. Mil documentos com cerca de mil palavras cada acrescentam 0.1 GB de dados do Postgres, o que é praticamente nada. Mil ficheiros carregados acrescentam 10 GB, e esse é o fator principal. Estes são valores de planeamento publicados, não medições de uma instância em execução. Considere-os uma indicação de proporção, não uma promessa. A proporção é o que importa: a base de dados mantém-se pequena, e os ficheiros carregados determinam o espaço em disco.

Escreva o ficheiro compose, fixando as tags

A instalação documentada descarrega um ficheiro pronto com curl -L -o docker-compose.yml https://github.com/toeverything/AFFiNE/releases/latest/download/docker-compose.yml. Funciona. Há um detalhe que deve conhecer antes de depender dele: em 8 de agosto de 2026, o ficheiro anexado à release 0.27.3 ainda obtinha os caminhos de um ficheiro .env, usando ${UPLOAD_LOCATION}, ${CONFIG_LOCATION} e ${DB_DATA_LOCATION}, enquanto a página de referência da documentação mostrava uma disposição mais recente, que coloca os dados de armazenamento e do PostgreSQL em ./data, mantém a configuração em ./config e não precisa de .env. A release 0.27.4 alterou o ficheiro anexado para essa disposição e, em 12 de outubro de 2026, os dois ficheiros já estavam alinhados. Se instalou a partir do ficheiro antigo, os seus caminhos continuam a vir de .env, e a página de atualização do AFFiNE avisa que substituir o ficheiro compose sem transportar essas definições pode iniciar o Postgres com um diretório de dados vazio. Escrever o ficheiro manualmente elimina essa dúvida. Também terá de o editar para fixar as imagens e definir uma palavra-passe da base de dados.

mkdir -p ~/affine/config ~/affine/data
cd ~/affine
printf 'DB_PASSWORD=%s\n' "$(openssl rand -hex 24)" > .env
chmod 600 .env

O Compose lê .env automaticamente a partir do diretório do projeto e substitui ${DB_PASSWORD}, para que a palavra-passe nunca apareça no ficheiro que colaria num tópico de suporte. Vale a pena manter esta prática em todas as stacks que executar. A explicação está em manter os segredos fora do ficheiro compose.

Agora escreva ~/affine/docker-compose.yml:

name: affine
services:
  affine:
    image: ghcr.io/toeverything/affine:stable
    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:${DB_PASSWORD}@postgres:5432/affine
      - AFFINE_INDEXER_ENABLED=false
    restart: unless-stopped

  affine_migration:
    image: ghcr.io/toeverything/affine:stable
    container_name: affine_migration_job
    command: ['sh', '-c', 'node ./scripts/self-host-predeploy.js']
    volumes:
      - ./data/storage:/root/.affine/storage
      - ./config:/root/.affine/config
    environment:
      - REDIS_SERVER_HOST=redis
      - DATABASE_URL=postgresql://affine:${DB_PASSWORD}@postgres:5432/affine
      - AFFINE_INDEXER_ENABLED=false
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy

  redis:
    image: redis:8-alpine
    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_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: affine
      POSTGRES_INITDB_ARGS: '--data-checksums'
    healthcheck:
      test: ['CMD', 'pg_isready', '-U', 'affine', '-d', 'affine']
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

Há quatro diferenças em relação ao ficheiro distribuído pelo projeto, e cada uma tem uma razão.

  • 127.0.0.1:3010:3010 publica a porta apenas no endereço de loopback. As versões do Docker anteriores a 28.0.0 permitem que hosts no mesmo segmento L2 alcancem portas publicadas em localhost. O encaminhamento direto configurado também pode expor a porta do contentor apesar da publicação em loopback. Use Docker 28.0.0 ou mais recente e mantenha o encaminhamento direto desativado durante a configuração. O '3010:3010' do projeto associa-se a todas as interfaces. Na maioria das imagens de VPS, isso inclui a interface pública.
  • POSTGRES_HOST_AUTH_METHOD: trust foi removido e foi definida uma palavra-passe. A autenticação de confiança aceita qualquer ligação a essa base de dados como o utilizador affine, sem palavra-passe. Fica limitada à rede privada do Compose. Isto deixa de ser seguro no dia em que ligar mais um contentor a essa rede ou publicar 5432 durante a depuração.
  • redis:8-alpine substitui um redis simples, que resolve para latest. Em agosto de 2026, isso corresponde ao Redis 8. A fixação mantém a versão principal testada e impede que uma futura versão Redis 9 seja instalada durante uma docker compose pull não relacionada.
  • pgvector/pgvector:pg16 mantém-se exatamente como definido pelo projeto, pela razão indicada acima.

POSTGRES_PASSWORD só é lido quando o Postgres cria o diretório de dados pela primeira vez. Numa instância que já exista, defina a palavra-passe com docker compose exec postgres psql -U affine -c "ALTER USER affine WITH PASSWORD 'yourpassword'" e atualize DATABASE_URL para corresponder.

A configuração está em config/config.json

O AFFiNE lê as definições de config/config.json, que é o diretório montado em /root/.affine/config. Nada cria esse ficheiro automaticamente, por isso escreva-o antes do primeiro arranque. Abra ~/affine/config/config.json num editor e introduza este conteúdo, substituindo o exemplo pelo seu domínio:

{
  "$schema": "https://github.com/toeverything/affine/releases/latest/download/config.schema.json",
  "server": {
    "name": "Team workspace",
    "externalUrl": "https://affine.example.com"
  },
  "copilot": {
    "enabled": false,
    "byok": {
      "enabled": false
    }
  }
}

server.externalUrl tem de ser o endereço que os seus utilizadores abrem efetivamente num navegador. O AFFiNE cria links de partilha e convites para áreas de trabalho a partir desse valor. Se ficar definido como http://localhost:3010, o convite enviado aponta o destinatário para a própria máquina dele e falha. Defina o endereço HTTPS público antes do primeiro arranque, para que o ficheiro e o painel de administração nunca apresentem valores diferentes. As definições alteradas posteriormente no painel de administração são guardadas na base de dados, não neste ficheiro.

copilot controla as funcionalidades de IA. copilot.byok.enabled ativa o uso de uma chave própria, permitindo que o proprietário de uma área de trabalho introduza a chave do seu fornecedor de modelos nas definições da área de trabalho. A instalação autónoma do AFFiNE não inclui uma subscrição de IA. Deixe ambos false se não quiser utilizar essa funcionalidade.

Inicie a stack:

docker compose up -d
docker compose ps --all

docker compose ps --all deve listar affine_postgres e affine_redis como saudáveis, affine_server como em execução e affine_migration_job com o estado exited (0). O --all é importante: sem ele, docker compose ps mostra apenas os contentores em execução, por isso um job de migração concluído corretamente não aparece. Qualquer outro código de saída no job de migração é o problema a investigar, e o respetivo log identifica o passo que parou:

docker compose logs affine_migration

Fixe a imagem antes de se esquecer

stable é uma tag mutável. O fluxo de lançamento do AFFiNE aponta várias tags para cada build estável, e duas são relevantes aqui: stable, que é alterada a cada lançamento, e stable- seguida do hash curto do git, que não é alterada. Se deixar stable, uma docker compose pull executada seis meses depois obtém uma imagem diferente e executa as migrações na sua base de dados num momento que não escolheu. Fixe a imagem exata que testou:

docker compose pull
docker image inspect ghcr.io/toeverything/affine:stable --format '{{index .RepoDigests 0}}'

Isto apresenta uma linha como ghcr.io/toeverything/affine@sha256: seguida de um hash longo. Cole a string completa na linha image: de affine e affine_migration. Os dois devem corresponder sempre, porque são a mesma imagem a desempenhar dois papéis. Uma diferença significa migrar a base de dados para um esquema e disponibilizá-la com outro. A atualização passa então a ser uma alteração deliberada, e não uma surpresa: altere o digest, faça uma cópia de segurança, docker compose pull, docker compose up -d.

Crie a conta de administração antes de qualquer outra pessoa

Abra /admin numa instância nova. O AFFiNE encaminha-o para uma página de criação de conta, porque o servidor ainda não tem administrador. Esse fluxo não usa código de convite nem token de configuração. A primeira pessoa a concluir o registo torna-se administradora do seu servidor. Por isso, a porta tem de permanecer fechada até concluir o registo.

É por isso que o ficheiro compose acima associa-se a 127.0.0.1. Aceda-lhe através de um túnel SSH a partir da sua própria máquina:

ssh -L 3010:127.0.0.1:3010 you@your-server-ip

Deixe esse processo em execução e abra http://127.0.0.1:3010/admin no navegador local. Registe-se e inicie sessão. Em seguida, feche o túnel. Só agora é seguro publicar a instância com um nome público. O mesmo risco existe noutras aplicações autoalojadas. É ainda maior quando o primeiro início de sessão cria uma passkey associada ao hostname. Por isso, o TLS e o domínio final têm de estar definidos antes da criação da primeira conta quando fizer autoalojamento do openGym.

Onde o AFFiNE guarda os seus dados

Três caminhos contêm tudo, e todos ficam dentro do diretório que criou.

  • ./data/postgres é o diretório de dados do Postgres: documentos, utilizadores, espaços de trabalho, permissões e quaisquer definições do servidor guardadas no painel de administração.
  • ./data/storage é montado em /root/.affine/storage no contentor e contém todos os ficheiros carregados.
  • ./config é montado em /root/.affine/config e contém config.json e private.key gerado pelo trabalho de migração.

O projeto upstream utiliza bind mounts aqui em vez de named volumes, e essa escolha é deliberada: pode criar tar e copiar estes caminhos com comandos normais, sem pedir ao Docker que indique onde os colocou. A desvantagem é que a propriedade dos ficheiros no host passa a ser da sua responsabilidade. Esse é o compromisso abordado em bind mounts e named volumes.

Como fazer backup do AFFiNE

É necessário fazer backup de duas coisas, e cada uma é armazenada de forma diferente. A base de dados é um servidor ativo, portanto copiar os ficheiros enquanto ela está em execução produz uma cópia corrompida. Em vez disso, faça um dump:

mkdir -p ~/affine/backup
cd ~/affine
docker compose exec -T postgres pg_dump --format c --username affine affine \
  > backup/affine-$(date +%F).dump
ls -lh backup/

O dump é executado dentro do contentor através do socket local, por isso não pede a palavra-passe. Verifique o tamanho no resultado de ls. Um ficheiro com apenas algumas centenas de bytes significa que o dump falhou, embora a shell tenha criado o ficheiro. Este é o tipo de falha que costuma ser descoberto seis meses depois. -T também é importante: sem essa opção, o Compose pode alocar um terminal e corromper o fluxo binário.

Os ficheiros carregados são ficheiros comuns, por isso compacte-os com tar:

tar czf backup/storage-$(date +%F).tgz -C data storage
tar czf backup/config-$(date +%F).tgz config docker-compose.yml .env

Mantenha o diretório config completo, o ficheiro do Compose e .env no backup, porque o dump não os contém. O job de migração também cria config/private.key, por isso copiar apenas config.json deixa esse ficheiro de fora. As definições guardadas no painel de administração são armazenadas na base de dados, portanto o dump acima já as inclui. Desde 12 October 2026, a página de backup do AFFiNE também permite exportar do painel de administração as definições do servidor que foram alteradas. Copie os três ficheiros para fora do servidor. Um backup no mesmo disco que o recurso protegido não é um backup. Esta separação entre um dump da base de dados e um tar do diretório de uploads é o padrão a repetir em todos os outros contentores com estado que executar. É também o padrão que mantém seguras o histórico de conversas e os anexos quando autoalojar o Chatwoot como central de suporte.

Restaurar, com uma armadilha nos passos publicados

Leia os passos oficiais de restauro antes de precisar deles e leia-os com atenção. Na versão publicada em agosto de 2026, os passos copiavam um ficheiro chamado affine.backup para o contentor e depois faziam o restauro a partir de ./pg.backup. Estes são dois nomes diferentes. Além disso, removiam um diretório ./postgres, enquanto o ficheiro compose atual mantém os dados em ./data/postgres. Desde 12 de outubro de 2026, a página usa affine.backup em todos os passos e indica que deve manter o diretório antigo do Postgres até confirmar o restauro. A documentação original já corrigiu a armadilha, mas uma cópia dos passos guardada antes dessa data ainda a contém. Em qualquer caso, siga os caminhos que utilizou efetivamente, e não os caminhos de um snippet. Esta é a sequência para a estrutura usada neste guia:

cd ~/affine
docker compose down
sudo mv data/postgres data/postgres.old
docker compose up -d postgres
docker compose cp backup/affine-2026-08-08.dump postgres:/tmp/affine.dump
docker compose exec postgres pg_restore --format c --username affine \
  --dbname affine --verbose /tmp/affine.dump
docker compose up -d

Observe o mv, e não um rm. Restaurar sobre uma base de dados da qual não guardou uma cópia é uma forma de transformar um comando incorreto em perda total de dados. Mover primeiro o diretório antigo para outro local não tem qualquer custo. Restaure também os uploads com tar xzf backup/storage-2026-08-08.tgz -C data. Caso contrário, todos os documentos serão apresentados com anexos danificados. Depois, inicie sessão e abra um documento que contenha uma imagem. Esse é o teste. Um restauro que ainda não abriu num browser é apenas um ficheiro, não uma cópia de segurança.

Colocar o AFFiNE atrás de um proxy já em execução

O AFFiNE utiliza WebSocket, e isso não é opcional. A documentação é clara: o WebSocket é a base do sistema de sincronização e colaboração do AFFiNE. Por isso, um proxy que não atualize essas ligações deixa-o com um workspace em que a edição deixa silenciosamente de sincronizar. A página carrega, o login funciona e uma edição feita num browser nunca chega ao outro. Nas ferramentas de desenvolvimento do browser, abra o separador Network e filtre por WS. Uma ligação que abre e fecha repetidamente indica que o proxy não está a encaminhar o upgrade.

Se já utiliza o Traefik para outros contentores, o AFFiNE entra nele como um serviço normal. Elimine o bloco ports: do serviço affine e adicione:

    networks:
      - default
      - proxy
    labels:
      - 'traefik.enable=true'
      - 'traefik.docker.network=proxy'
      - 'traefik.http.routers.affine.rule=Host(`affine.example.com`)'
      - 'traefik.http.routers.affine.entrypoints=websecure'
      - 'traefik.http.routers.affine.tls.certresolver=letsencrypt'
      - 'traefik.http.services.affine.loadbalancer.server.port=3010'

No fim do ficheiro, junto de services:, adicione:

networks:
  proxy:
    external: true

O nome do certificate resolver tem de corresponder ao definido na configuração do Traefik, e loadbalancer.server.port é a porta 3010 do contentor, nunca uma porta do host. O Traefik encaminha ligações WebSocket sem configuração adicional, por isso não é necessário adicionar mais nada. Se o resto da sua stack já estiver atrás do Authentik para início de sessão único, um middleware de forward auth neste router controlará o acesso do browser ao AFFiNE. No entanto, mantenha-o desativado até testar a aplicação desktop, que não transporta uma sessão do browser e simplesmente falhará a sincronização. A execução de várias aplicações atrás de uma única instância está descrita em um único Traefik à frente de várias aplicações.

No nginx, é necessário pedir explicitamente o upgrade:

location / {
    proxy_pass http://127.0.0.1:3010;
    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;
    client_max_body_size 100m;
}

client_max_body_size tem o valor predefinido de 1 MB no nginx. Sem essa linha, qualquer upload maior do que uma fotografia pequena falha com o estado 413 e nada aparece nos logs do AFFiNE, porque o pedido nunca chegou ao serviço. O Caddy precisa de uma linha, reverse_proxy http://127.0.0.1:3010, e trata dos certificados e dos upgrades WebSocket automaticamente.

O que a instalação self-hosted não inclui

Seja realista sobre isto antes de transferir uma equipa.

A colaboração em tempo real está disponível e é a funcionalidade a que se referem todas as recomendações de dimensionamento, porque a documentação da AFFiNE atribui o consumo de memória ao sistema de sincronização e à fusão de documentos. A edição offline é a razão por que muitas pessoas querem uma ferramenta local-first, e a aplicação de desktop pode adicionar o seu servidor self-hosted à lista de workspaces e iniciar sessão nele. Teste o comportamento offline exato de que a sua equipa depende antes de avançar: edite na aplicação de desktop com a rede desligada, volte a ligar a rede e verifique o resultado num segundo dispositivo. As listas de funcionalidades não são prova, e isto também se aplica a esta lista.

A pesquisa de texto integral no servidor está desativada no ficheiro compose fornecido, onde AFFINE_INDEXER_ENABLED=false está definido no servidor e no job de migração. Ativá-la implica adicionar um contentor Manticore Search, que passa a ser um quinto serviço e consome mais memória. Num servidor com 2 GB, esta é a alteração que faz ultrapassar o limite. A pesquisa dentro do cliente continua a funcionar no workspace que está aberto.

Há dois limites que deve conhecer antes de convidar pessoas. Um workspace self-hosted pode ter no máximo 10 utilizadores, e ultrapassar esse limite requer uma licença Team da AFFiNE. O armazenamento ilimitado de blobs e o tamanho ilimitado dos blobs para instâncias self-hosted são descritos na documentação como funcionalidades previstas, mas ainda não totalmente implementadas, conforme verificado em outubro de 2026. Nenhum destes limites é relevante para uma utilização doméstica ou para uma equipa pequena. Ambos são relevantes se planeava transferir quarenta pessoas.

Atualizações

Leia primeiro as notas da versão, sobretudo numa atualização de versão menor, como de 0.26 para 0.27, porque podem ser introduzidas alterações incompatíveis. Faça uma cópia de segurança da base de dados e do diretório de armazenamento antes de alterar qualquer coisa, porque o job de migração modifica o schema no arranque seguinte e não existe forma de desfazer a alteração. Depois, altere o digest fixado, execute docker compose pull seguido de docker compose up -d e monitorize docker compose logs -f affine_migration até terminar sem erros. docker image prune remove as layers antigas depois disso. Uma nota histórica para instalações muito antigas: a partir da versão 0.23.0, o nome da imagem mudou de affine-graphql para affine. Por isso, um ficheiro compose anterior a essa versão precisa de ter as linhas da imagem reescritas antes de um pull conseguir encontrar a imagem. A partir da versão 0.27.4, o template compose oficial também mudou os paths do Postgres, dos uploads e da configuração, além de alterar a ligação predefinida à base de dados. Compare o ficheiro novo com o seu em vez de o copiar para uma instalação em funcionamento.

FAQ

Por que o contentor AFFiNE nunca inicia?

O serviço affine declara condition: service_completed_successfully na tarefa affine_migration. Se a migração terminar com qualquer estado diferente de 0, o servidor nunca é iniciado e não aparece qualquer interface web. Execute docker compose logs affine_migration para ver qual passo parou. Confirme que um ficheiro Compose editado manualmente continua a usar pgvector/pgvector:pg16. O Postgres padrão não inclui a extensão pgvector que o esquema do AFFiNE ainda declara, embora a v0.27.4 já não defina os quatro modelos de embedding presentes na v0.27.3.

De quanta RAM precisa o AFFiNE alojado localmente?

A página de requisitos do AFFiNE pede pelo menos 4 núcleos de CPU e 2 GB de RAM. O requisito sobe para 4 GB quando os documentos ultrapassam 10,000 palavras. A página também indica que a intercalação de um documento com 10,000 modificações pode atingir 1 GB. Num servidor com 2 GB, esse pico é o que causa o problema, não a carga em espera. O mecanismo de encerramento por falta de memória do kernel escolhe um processo para parar, provavelmente o servidor AFFiNE, por ser o maior. Em seguida, restart: unless-stopped volta a iniciá-lo, pelo que os utilizadores veem o recarregamento da página em vez de um erro. Confirme com docker inspect affine_server --format '{{.State.OOMKilled}}' e sudo dmesg -T | grep -i 'out of memory'. Depois, adicione um ficheiro de swap de 2 GB como margem para picos curtos e passe para 4 GB de RAM se os encerramentos continuarem.

Onde o AFFiNE armazena os meus dados e o que devo salvaguardar?

Três caminhos no diretório do Compose contêm tudo: ./data/postgres para a base de dados, ./data/storage para os ficheiros carregados e ./config para config.json e private.key gerado. Faça a cópia de segurança da base de dados com docker compose exec -T postgres pg_dump --format c --username affine affine > affine.dump, em vez de copiar os ficheiros, porque não é seguro copiar um Postgres em execução. Esse dump também inclui as definições guardadas no painel de administração, porque o AFFiNE armazena essas definições na base de dados. Crie um tar de ./data/storage para os carregamentos e mantenha o diretório config completo, o ficheiro Compose e .env, porque esses ficheiros não fazem parte da base de dados.

A colaboração em tempo real funciona num AFFiNE alojado localmente?

Sim, e não é necessário ativar nada. O único requisito é o reverse proxy, porque a sincronização usa ligações WebSocket. No nginx, isso significa proxy_http_version 1.1, juntamente com os cabeçalhos Upgrade e Connection: upgrade. O Traefik e o Caddy encaminham essas ligações sem configuração adicional. O sintoma de um proxy que não faz o upgrade dessas ligações é um workspace que carrega e permite iniciar sessão normalmente, enquanto as edições feitas num browser nunca aparecem noutro.

Posso executar o AFFiNE com uma imagem Postgres padrão?

Use pgvector/pgvector:pg16, que é o Postgres 16 com a extensão pgvector compilada. O AFFiNE v0.27.4 ainda declara extensions = [pgvector(map: "vector")] em schema.prisma, mas já não define os quatro modelos com colunas de embedding vector(1024) presentes na v0.27.3. O Postgres padrão não inclui a extensão declarada. Se apontar o AFFiNE para um servidor Postgres externo, instale nele o pgvector e crie a extensão na base de dados de destino antes de executar a migração.

Este artigo foi útil?