Mapa da trilha
Conteúdo detalhado
📋 Pré-requisitos
Tudo que você precisa ter pronto antes de tocar no docker-compose: máquina, rede, email e armazenamento.
Servidor virtual Linux (Ubuntu 22.04+) com pelo menos 2 GB de RAM e 1 vCPU. Postiz + Postgres + Redis cabem confortavelmente em CX22 da Hetzner (~€4) ou Droplet básico ($6) da DigitalOcean.
Self-hosted exige máquina sempre ligada com IP público. Escolher mal o tamanho gera OOM kill do Postgres; escolher caro demais é desperdício para uso pessoal/pequena equipe.
2 GB RAM mínimo, 20 GB SSD, IPv4 público, snapshots semanais, firewall (UFW) liberando 22/80/443.
Docker Engine roda os containers; Docker Compose v2 (integrado como plugin) orquestra múltiplos serviços via docker-compose.yml. Instalação oficial: curl -fsSL https://get.docker.com | sh.
Postiz oficialmente recomenda compose como deploy padrão. Sem isso, você teria que rodar Postgres, Redis, frontend e workers separadamente — dor de cabeça desnecessária.
docker --version ≥ 24, plugin compose v2 (docker compose sem hífen), usuário no grupo docker, restart policies.
Subdomínio apontando (registro A) para o IP do seu VPS. Necessário para HTTPS via Let's Encrypt e para OAuth funcionar (X e LinkedIn exigem callback HTTPS).
Sem domínio, callbacks OAuth quebram, cookies de sessão falham em produção e você fica preso a HTTP em IP cru — inseguro e impossível de usar com X/LinkedIn.
Registro A, TTL baixo durante setup, Cloudflare proxy desligado para gerar cert, propagação DNS (5-30 min).
Provedor SMTP/API para Postiz mandar email de boas-vindas, reset de senha e convites de membros. Resend é grátis até 3.000 emails/mês e tem API key simples.
Sem email configurado você não consegue convidar ninguém pra organização nem recuperar senha — efeitos só aparecem quando você precisa.
SMTP host, porta 587 (STARTTLS), API key Resend, registros SPF/DKIM no DNS, FROM_EMAIL_ADDRESS no .env.
Bucket S3-compatible para armazenar imagens e vídeos dos posts. R2 da Cloudflare não cobra egress; MinIO roda no próprio compose e fica em /var/lib/minio.
Postiz precisa servir mídia via URL pública para X/Bluesky baixarem. Salvar local no container quebra em reinicializações e estoura disco rápido.
Access key, secret key, endpoint URL, bucket público, CORS liberando o domínio do Postiz.
Conforto para abrir SSH, editar arquivos (.env, docker-compose.yml), ler logs (docker compose logs -f) e reiniciar serviços (docker compose restart postiz).
Self-hosted = você é o sysadmin. Quando algo quebra (e vai quebrar uma vez), saber ler log e editar variável é a diferença entre 10 min e 3h.
SSH com chave (não senha), tail -f, grep ERROR, screen/tmux para sessões longas.
🚀 Setup com Docker Compose
Do git clone ao docker compose up -d: o módulo exemplar com cada arquivo dissecado.
Arquivo YAML que declara 3 serviços: postiz (app), postgres e redis. Imagem oficial em ghcr.io/gitroomhq/postiz-app:latest, porta 5000 exposta internamente e mapeada para 5000 do host (ou via reverse proxy).
Saber editar esse arquivo é o que separa "instalei o Postiz" de "rodo Postiz em produção". Healthchecks, volumes nomeados e depends_on evitam corrida de start-up.
services, volumes nomeados (postgres-data), depends_on com condition: service_healthy, restart: unless-stopped, env_file: .env.
Arquivo .env com segredos e configuração: JWT_SECRET (gerar com openssl rand -hex 32), MAIN_URL=https://postiz.seudominio.com, DATABASE_URL=postgresql://postiz:senha@postgres:5432/postiz, REDIS_URL=redis://redis:6379, credenciais S3 e SMTP.
90% dos erros de "Postiz não sobe" são variável faltando ou URL errada (http vs https, localhost vs nome do service). Dominar o .env é dominar 80% do troubleshooting.
.env nunca no git (use .env.example), nome do service Docker é o hostname dentro da rede, NEXT_PUBLIC_* vai pro browser, secrets em 32+ caracteres.
Postgres 14+ guarda contas conectadas, posts agendados e organizações. Redis (7+) faz filas BullMQ dos jobs de publicação e cache de sessão. Ambos como containers irmãos do Postiz com volumes nomeados.
Sem volume nomeado, você perde TODOS os posts agendados no primeiro docker compose down -v. Sem Redis, jobs de publicação não rodam — posts ficam "scheduled" para sempre.
Volumes postgres-data:/var/lib/postgresql/data, healthcheck pg_isready, Redis sem persistência (efêmero ok), backup diário do volume.
Comando docker compose up -d em modo detached. Na primeira execução baixa imagens (~1.5 GB), cria volumes, sobe Postgres, roda migrations Prisma automaticamente e inicia o frontend na porta 5000.
Saber a sequência esperada (pull → create volume → start postgres → wait healthy → migrate → start postiz) ajuda a identificar onde travou se algo der errado.
Flag -d (detached), --build para imagem custom, --force-recreate após editar .env, primeira subida demora 3-5 min.
Comandos docker compose logs -f postiz (follow), --tail 200 para últimas linhas, docker compose ps para estado e docker stats para uso de CPU/RAM por container.
Erros comuns na primeira subida: ECONNREFUSED (Redis ainda subindo), migration falhou (DB credentials), porta 5000 já em uso. Todos visíveis no log.
Filtrar por service, separar stdout/stderr, exportar log com > postiz.log, log rotation no daemon.json.
Três checks: (a) docker compose ps mostra 3 serviços Up (healthy); (b) curl http://localhost:5000 devolve HTML do Next.js; (c) abrir https://postiz.seudominio.com mostra tela de login.
"Está rodando" é diferente de "está funcionando". Validar saúde antes de chamar a tropa garante que você não vai descobrir erro só quando o primeiro usuário tentar logar.
Endpoint /api/health, status code 200, conexão DB OK no log, screenshot da tela de signup salvo.
🔧 Primeiro acesso e configuração
Container subiu — agora é hora de criar usuário, configurar storage na UI e montar a primeira organização.
Postiz não tem "admin global" — o primeiro signup vira automaticamente owner da primeira organização criada. Abra /auth/register, preencha email, senha e confirme via email (se SMTP estiver configurado).
Quem cria primeiro tem o controle. Em deploy interno, registre você antes de divulgar a URL para a equipe, ou alguém vira owner por engano.
DISABLE_REGISTRATION=true após primeiro user, role owner ≠ admin, recuperação de senha via email transacional.
3 áreas principais: Launches (calendário visual e agendamento), Analytics (métricas por rede e por post), Settings (organização, integrações, billing). Sidebar à esquerda navega entre elas.
Conhecer o mapa da UI poupa minutos por dia. Calendário arrastável é o ponto forte do Postiz e está no Launches.
Drag-and-drop no calendário, modal de "novo post" multi-rede, dark mode nativo, switch entre organizações no topo.
Em Settings → Storage Provider, escolha entre local (não recomendado) ou cloudflare. Cole endpoint R2, access key, secret key, bucket name e a URL pública. Teste upload de imagem.
Storage mal configurado = posts com imagem ficam sem mídia. X retorna 400 silenciosamente quando URL da imagem é privada.
CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_ACCESS_KEY, CLOUDFLARE_BUCKETNAME, CLOUDFLARE_BUCKET_URL, CORS allow-origin.
No .env: EMAIL_PROVIDER=resend + RESEND_API_KEY=re_xxx + EMAIL_FROM_NAME=Postiz + EMAIL_FROM_ADDRESS=noreply@seudominio.com. Reinicie e teste mandando convite para si mesmo.
Sem email, "convidar membro" só gera link manual. SPF/DKIM faltando = email vai pra spam ou nem entrega.
Resend domain verification, SPF TXT record, DKIM CNAME, FROM com domínio próprio (não @gmail).
Organization é o "workspace" do Postiz: agrupa contas de redes sociais, membros e posts. Você pode ter várias (ex: "Pessoal" e "ClienteX"). Criada automaticamente no primeiro signup; novas em Settings → Organizations.
Separação por organização é o que permite gerenciar múltiplos clientes/projetos sem misturar contas conectadas e analytics.
Org = boundary de billing, role no nível da org, switch via dropdown, slug único.
Em Settings → Team Members, click "Invite", informe email e role. Postiz envia email com link de aceite que cria a conta vinculada à org. Roles: admin (tudo menos billing), user (cria/edita posts), viewer (só lê).
Compartilhar credencial é antipattern. Cada pessoa com login próprio gera histórico auditável de quem publicou o quê.
Convite expira em 7 dias, role editável depois, remover membro não apaga posts dele, audit log por usuário.
🔌 Conectando contas
A parte que importa: plugar X, Bluesky, LinkedIn e Mastodon — e ver o primeiro post sair pra cada rede.
Bluesky usa ATProto, não OAuth. Em Launches → "+", escolha Bluesky, informe handle (ex: você.bsky.social) e um app password criado em bsky.app/settings/app-passwords. Conexão é instantânea, sem callback.
É a conta mais fácil de plugar — bom primeiro teste end-to-end. Se Bluesky funciona, o pipeline (DB → fila → publisher) está ok.
App password ≠ senha principal, revogável independentemente, sem rate limit agressivo, 300 chars por post.
Crie app no developer.x.com, copie Client ID/Secret para X_API_KEY e X_API_SECRET no .env, configure callback https://postiz.seudominio.com/integrations/social/x. No Postiz, click "Connect X" e autorize.
X é a rede mais quebradiça: callback URL exata, scopes específicos (tweet.write, users.read), API v2 paga limita free tier.
OAuth 2.0 com PKCE, refresh token rotativo, free tier = 1.500 posts/mês, callback case-sensitive.
Crie app em linkedin.com/developers, peça produtos "Share on LinkedIn" e "Sign In with LinkedIn using OpenID Connect". Configure LINKEDIN_CLIENT_ID / LINKEDIN_CLIENT_SECRET no .env. No Postiz, escolha entre conta pessoal e Company Page (admin obrigatório).
LinkedIn é a rede com maior fricção: aprovação manual de "Share on LinkedIn" pode demorar dias. Iniciar cedo no curso.
Scope w_member_social, w_organization_social para páginas, token expira em 60 dias.
Mastodon é federado: cada instância (ex: mastodon.social, tech.lgbt) tem OAuth próprio. No Postiz, informe a URL da sua instância, ele cria app via API, redireciona e finaliza. Sem developer portal centralizado.
É a única rede onde você não depende de aprovação da big tech. Ótimo para teste sem risco e para audiência tech.
Endpoint /api/v1/apps registra app dinamicamente, scopes read write, 500 chars padrão (varia por instância).
No calendário Launches, click em um horário futuro, selecione TODAS as contas conectadas, escreva o post, opcionalmente customize por rede (botão "Customize per channel"), confirme. Postiz cria N jobs Redis (um por rede) com publish_at futuro.
Este é o "Hello World" do Postiz. Funcionou em 4 redes simultâneas = self-host está validado.
Per-channel content (X tem 280, LinkedIn 3000), upload de mídia uma vez, timezone do user, retry automático em falha temporária.
Aba Analytics agrega métricas que cada rede expõe via API: impressões (X, LinkedIn), reposts (Bluesky), favorites (Mastodon). Atualização ~6h após publicar. Filtros por rede, por período e por post.
Sem dados, "agendar" é só automação cega. Cruzar horário × engajamento revela quando vale postar.
Métricas variam por rede (LinkedIn não dá impressão fora de Pages), cache de 6h, export CSV, comparação org-wide.
🔗 API e webhooks
Postar pela UI é manual. A API REST do Postiz transforma sua instância em um endpoint pra qualquer app que você quiser.
Postiz expõe rotas /public/v1/* protegidas por API key. Habilitar via API_LIMIT=30 (req/min) e DISABLE_PUBLIC_API=false no .env. Reinicie o container após.
Sem API, Postiz é só dashboard. Com API, vira backend headless de publicação para seus apps.
Rate limit por API key, escopo por org, Swagger disponível em /docs, versionamento via prefix.
Em Settings → API, click "Generate new key", dê um label (ex: "n8n-prod") e copie o token mostrado UMA vez (formato pk_live_...). Postiz armazena hash, não o token cru — perdeu = gera outro.
Uma chave por integração permite revogar uma sem afetar as outras quando algo vazar.
Token visible only once, vinculado à org, sem expiração default, last_used_at para auditoria.
Comando: curl -X POST https://postiz.seudominio.com/public/v1/posts -H "Authorization: pk_live_xxx" -H "Content-Type: application/json" -d '{"type":"schedule","date":"2026-06-01T15:00:00Z","posts":[{"integration":{"id":"INT_ID"},"value":[{"content":"Hello from API"}]}]}'. Resposta 201 com ID do post.
Se curl funciona, qualquer linguagem funciona. É o teste irredutível de que a API está acessível e a key é válida.
Header Authorization sem "Bearer", integration ID obtido em GET /integrations, type=schedule|now|draft, date em ISO 8601 UTC.
Em Settings → Webhooks, cadastre uma URL HTTPS sua. Postiz POSTa JSON quando eventos ocorrem: post.published (com URL final na rede), post.failed (com mensagem de erro), post.scheduled.
Polling /posts/:id pra saber se publicou é gambiarra. Webhook = sua app reage em tempo real ao sucesso/falha sem desperdiçar request.
Retry com backoff exponencial em 5xx, assinatura HMAC opcional, idempotency key no payload, timeout 10s.
No n8n, HTTP Request node com método POST, URL da sua instância, header Authorization (credential reutilizável), body JSON dinâmico via expressões. Make idem com módulo HTTP. Zero plugin específico necessário — REST puro.
É o caminho mais rápido para automatizar "post toda terça às 10h com últimos posts do RSS" sem escrever uma linha de código.
Credential type "Header Auth", trigger por cron/RSS/Airtable, tratamento de erro com IF node, logs em Supabase.
Pipeline típico: (1) seu CMS/Notion/Airtable cria conteúdo → (2) trigger (webhook/cron) chama POST /public/v1/posts com agendamento → (3) Postiz coloca na fila Redis → (4) na hora marcada publica em N redes → (5) webhook avisa sua app com URL final → (6) sua app guarda histórico.
É a arquitetura que separa "ferramenta isolada" de "componente do meu stack". Vale para SaaS, blog automation, agência multi-cliente.
Idempotência por external_id, dead-letter queue para falhas persistentes, retry com circuit breaker, observabilidade end-to-end (request_id propagado).