SSD Nodes Learn Hosting plans →
Guias Matt ConnorPor Matt Connor · Atualizado 2026-08-27

Como hospedar o AFFiNE com Docker Compose

Execute o AFFiNE em um VPS com Docker Compose: quatro containers, imagens fixadas, dados, backups e o que 2 GB de RAM realmente suportam na versão 0.27.3.

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

Alojando o AFFiNE por conta própria, obtém um workspace ao estilo do Notion num servidor que controla, executado em quatro contentores: a aplicação, um job de migração executado uma vez, Postgres e Redis. A colaboração em tempo real está incluída, até aos 10 lugares que um workspace self-hosted obtém por predefinição. A instalação consiste num ficheiro compose e num ficheiro de configuração JSON. É necessário decidir as tags das imagens, a organização do disco, o limite de memória e o proxy colocado à frente.

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 estiver a decidir o que executar, leia primeiro a comparação de alternativas self-hosted ao Notion. Este guia parte do princípio de que a escolha já foi feita e aborda a execução correta do AFFiNE, 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.

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. Em schema.prisma, a fonte de dados declara extensions = [pgvector(map: "vector")], e quatro tabelas têm uma coluna embedding tipada como vector(1024). O job de migração cria essas tabelas independentemente de você ativar ou não os recursos de IA. Por isso, a extensão já precisa existir no banco de dados antes que a migração possa terminar. Se você substituir por postgres:16, a extensão desaparece, a migração não consegue criar essas colunas e o servidor fica à espera de um job que falhou.

O AFFiNE passou a usar a imagem pgvector na versão 0.21. Numa instalação mais antiga que essa, editar a linha da imagem não é suficiente para concluir a atualização. Por isso, leia a página de atualização na documentação do AFFiNE para self-hosting antes de fazer qualquer pull.

Há mais um ponto sobre essa tag. pg16 significa Postgres 16, e a versão principal do Postgres não é um número que possa ser simplesmente incrementado. Se você a alterar para pg17 usando 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 mudança de versão principal exige um dump e uma restauração num diretório de dados novo.

Quanta CPU e RAM o AFFiNE self-hosted 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 aumenta 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.

Agora compare isso com um plano de 2 GB usado por duas pessoas. A 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 pedir 1 GB além de tudo o que já está residente. Num servidor com 2 GB e sem swap, o kernel responde a esse pedido com o terminador de falta de memória (OOM), que mata o processo maior. Neste caso, é o servidor AFFiNE.

O seu colega não vê um erro. Vê a página recarregar, porque restart: unless-stopped coloca o contentor novamente 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 no primeiro comando, ou uma linha Killed process que indique node no segundo, significa que ficou sem memória, e não que encontrou um bug. Corrija o problema dos dois lados. Primeiro, adicione swap, para que um pico torne o sistema lento em vez de o fazer falhar:

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 agora indicar um total de 2.0Gi de swap. A swap não torna o AFFiNE rápido, nem é essa a sua finalidade. Transforma um pico de um segundo num segundo lento, em vez de resultar num contentor parado. O outro lado da correção é impedir que o Postgres faça crescer a cache até 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.

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

ChartPublished AFFiNE storage figures, August 2026
The data behind this chart
[
  {
    "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 aproximadamente mil palavras cada acrescentam 0.1 GB de dados do Postgres, o que é quase nada. Mil ficheiros enviados acrescentam 10 GB. Esse é o principal fator. Estes são valores de planeamento publicados, e não medições de uma instância em execução. Trate-os como uma indicação de escala, não como uma garantia. A escala é o que importa: a base de dados permanece pequena, e os ficheiros enviados determinam o espaço em disco.

Escreva você mesmo o ficheiro compose, com as tags fixadas

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. Isso 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 lê 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 mostra uma estrutura mais recente, que mantém tudo em ./data e não precisa de .env. As duas opções são válidas. Escrever o ficheiro você mesmo elimina a dúvida. Também terá de o editar para fixar as imagens e definir uma palavra-passe para a 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} por si. Assim, a palavra-passe nunca aparece no ficheiro que colaria num tópico de suporte. Vale a pena manter esse hábito 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 upstream. Cada uma tem uma razão.

  • 127.0.0.1:3010:3010 publica a porta apenas no endereço de loopback. Assim, nada fora do servidor consegue aceder ao AFFiNE até decidir como o expor. O '3010:3010' do upstream associa-se a todas as interfaces. Na maioria das imagens VPS, isso inclui a interface pública.
  • POSTGRES_HOST_AUTH_METHOD: trust foi removido e foi definida uma palavra-passe. A autenticação trust aceita qualquer ligação a essa base de dados como o utilizador affine, sem palavra-passe. Isto fica limitado à rede privada do Compose. Deixa de ser seguro no dia em que adicionar outro contentor a essa rede ou publicar a porta 5432 durante uma operação de diagnóstico.
  • redis:8-alpine substitui um redis sem versão, que resolve para latest. Em agosto de 2026, isso corresponde ao Redis 8. A versão fixada mantém a versão principal que testou e impede que o Redis 9 seja instalado durante uma atualização docker compose pull não relacionada.
  • pgvector/pgvector:pg16 permanece exatamente como definido pelo upstream, pelo motivo indicado acima.

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

Configuração em config/config.json

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

{
  "$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 utilizadores realmente abrem 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, um convite enviado aponta o destinatário para a própria máquina e falha. Defina-o como o endereço HTTPS público antes do primeiro arranque, para que o ficheiro e o painel de administração nunca apresentem valores diferentes.

copilot controla as funcionalidades de IA. copilot.byok.enabled ativa a utilização de uma chave própria e permite ao proprietário de uma área de trabalho introduzir 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

docker compose ps deve apresentar affine_postgres e affine_redis como saudáveis, affine_server como em execução e affine_migration_job com o estado exited (0). Qualquer outro código de saída no job de migração requer investigação. 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 administrador antes de qualquer outra pessoa

Abra /admin numa instância nova e 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 inclui código de convite nem token de configuração. A primeira pessoa a carregar essa página torna-se administradora do servidor, por isso a porta deve permanecer fechada até concluir o registo.

Por esse motivo, o ficheiro compose associa-se a 127.0.0.1. Aceda-lhe através de um túnel SSH a partir do seu próprio computador:

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

Mantenha o túnel em execução e abra http://127.0.0.1:3010/admin no navegador local. Registe-se e inicie sessão; depois, feche o túnel. Só nesse momento é seguro disponibilizar a instância através de um nome público. A mesma condição de corrida existe noutras aplicações auto-hospedadas e é mais grave quando o primeiro início de sessão cria uma passkey associada ao nome do host. Por isso, o TLS e o domínio final têm de estar definidos antes da criação da primeira conta quando auto-hospeda o openGym.

Onde o AFFiNE armazena os seus dados

Três caminhos contêm todos os dados, e estão dentro do diretório que criou.

  • ./data/postgres é o diretório de dados do Postgres: documentos, utilizadores, áreas de trabalho e permissões.
  • ./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.

O projeto upstream usa bind mounts neste caso em vez de volumes nomeados, e essa escolha é deliberada: pode criar um tar e copiar estes caminhos com comandos comuns, sem pedir ao Docker que indique onde os colocou. A desvantagem é que a propriedade dos ficheiros no host passa a ser da sua responsabilidade. Essa é a troca abordada em bind mounts e volumes nomeados.

Como fazer backup do AFFiNE

É necessário fazer backup de duas coisas, usando métodos diferentes. O banco de dados é um servidor ativo, portanto copiar os respetivos ficheiros enquanto 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 na saída de ls. Um ficheiro com algumas centenas de bytes significa que o dump falhou, embora a shell tenha criado o ficheiro. Este é o tipo de falha que só é 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 apenas ficheiros, por isso faça um tar:

tar czf backup/storage-$(date +%F).tgz -C data storage
cp config/config.json backup/config-$(date +%F).json

Mantenha o config.json no seu backup manual. A documentação do AFFiNE ainda indica que a exportação da configuração pelo painel de administração não está implementada, conforme verificado em agosto de 2026. Por isso, o ficheiro no disco é a única cópia das suas definições. Copie os três ficheiros para fora do servidor. Um backup no mesmo disco que os dados protegidos 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 o histórico das conversas e os anexos seguros quando aloja o Chatwoot por conta própria como central de suporte.

Restauração e uma armadilha nos passos publicados

Leia os passos oficiais de restauração antes de precisar deles e leia-os com atenção. Na versão publicada em agosto de 2026, os passos copiam um ficheiro chamado affine.backup para o contentor e depois fazem a restauração a partir de ./pg.backup. Estes são dois nomes diferentes. Também removem um diretório ./postgres, enquanto o ficheiro compose atual mantém os dados em ./data/postgres. Siga os caminhos que utilizou efetivamente, em vez dos caminhos do exemplo. 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 uso de mv, e não de rm. Fazer a restauração 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 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 quebrados. Depois, inicie sessão e abra um documento que contenha uma imagem. Esse é o teste. Uma restauração que ainda não abriu num browser é 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 honesto consigo antes de migrar uma equipa.

A colaboração em tempo real está disponível. É a funcionalidade a que se referem todas as recomendações de dimensionamento, porque a documentação do próprio AFFiNE atribui o uso 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. A aplicação para desktop pode adicionar o seu servidor self-hosted à lista de espaços de trabalho e iniciar sessão nele. Teste exatamente o comportamento offline de que a sua equipa depende antes de se comprometer: edite na aplicação para desktop com a rede desligada, volte a ligar-se e verifique o resultado num segundo dispositivo. As listas de funcionalidades não são provas, e isto também se aplica a esta lista.

A pesquisa de texto completo 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. Esse é o quinto serviço e aumenta o consumo de memória. Num servidor com 2 GB, esta alteração é a que faz exceder o limite. A pesquisa dentro do cliente continua a funcionar no espaço de trabalho que está aberto.

Convém conhecer dois limites antes de convidar pessoas. Um espaço de trabalho self-hosted pode ter, no máximo, 10 lugares. Para ultrapassar esse limite, é necessária uma licença Team do 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 August 2026. Nenhum destes limites é relevante para uma família ou uma equipa pequena. Ambos são relevantes se planeava migrar quarenta pessoas.

Atualizações

Leia primeiro as notas de versão, sobretudo quando fizer uma atualização de versão secundária, 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 a tarefa de migração altera o esquema na inicialização seguinte e não existe uma operação de desfazer. 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 camadas antigas depois disso. Uma nota histórica para quem utiliza uma instalação muito antiga: 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 encontrar a imagem.

FAQ

Por que o contentor AFFiNE nunca arranca?

O serviço affine declara condition: service_completed_successfully na tarefa affine_migration. Por isso, se a migração terminar com qualquer estado diferente de 0, o servidor nunca arranca e não aparece qualquer interface web. Execute docker compose logs affine_migration para ver em que passo o processo parou. A causa mais comum num ficheiro compose editado manualmente é usar uma imagem postgres padrão em vez de pgvector/pgvector:pg16. O esquema do AFFiNE declara a extensão pgvector e cria tabelas com colunas vector(1024) que o Postgres simples não consegue criar.

De quanta RAM precisa o AFFiNE autoalojado?

A página de requisitos do AFFiNE pede pelo menos 4 núcleos de CPU e 2 GB de RAM. Esse valor 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 alterações pode atingir um pico de 1 GB. Num servidor com 2 GB, é esse pico que causa a falha, não a carga em inatividade. O killer de falta de memória do kernel termina o processo do AFFiNE, e restart: unless-stopped volta a iniciá-lo. Por isso, os utilizadores veem a página recarregar em vez de receberem 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 para que um pico torne o sistema lento em vez de o fazer falhar.

Onde armazena o AFFiNE os meus dados e do que devo fazer cópias de segurança?

Três caminhos no diretório do compose contêm todos os dados: ./data/postgres para a base de dados, ./data/storage para os ficheiros enviados e ./config para config.json. Faça uma 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. Um Postgres em execução não pode ser copiado com segurança dessa forma. Crie um arquivo tar de ./data/storage para os ficheiros enviados e guarde manualmente uma cópia de config.json. Em agosto de 2026, a exportação da configuração pelo painel de administração continua indicada como não implementada.

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

Sim. 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 alterações feitas num navegador nunca aparecem noutro.

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

Não. O schema.prisma do AFFiNE declara extensions = [pgvector(map: "vector")] e define quatro tabelas com uma coluna embedding do tipo vector(1024). A tarefa de migração cria essas tabelas mesmo quando as funcionalidades de IA estão desativadas. Use pgvector/pgvector:pg16, que é o Postgres 16 com essa extensão compilada. Se, em vez disso, 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.