Como Instalar o Dify no Portainer para Iniciantes – LowCode

Como Instalar o Dify no Portainer com Docker Swarm (Passo a Passo Sem Erros)

Nesta aula vamos instalar o Dify do zero na sua VPS, usando Portainer, Docker Swarm e Traefik. Diferente de tutoriais genéricos, aqui cada passo existe porque resolve um erro real que trava a instalação — então siga na ordem, mesmo que pareça um passo “óbvio”.


Pré-requisitos

Antes de começar, seu ambiente já precisa ter rodando:

  • Portainer (gerenciador do Docker Swarm)
  • Traefik (proxy reverso, com rede traefik_public e resolver de SSL configurado, ex: le)
  • PostgreSQL com pgvector (imagem pgvector/pgvector:pg16), acessível na rede digital_network pelo hostname postgres
  • Redis, acessível na mesma rede pelo hostname redis

Se você ainda não tem esses serviços, monte-os antes de seguir. O Dify depende deles para subir.

Se for usar um servidor novo: prefira datacenter no Brasil para reduzir a latência da aplicação.


Passo 1: Autenticar no Docker Hub (evita o erro mais comum)

Antes de qualquer coisa, faça login no Docker Hub direto no terminal do servidor:

docker login

Use uma conta gratuita (crie em hub.docker.com se não tiver).

Por que esse passo existe: o Docker Hub limita quantas imagens você pode baixar por hora sem estar logado. Como o Dify usa várias imagens grandes (api, web, sandbox, plugin_daemon), é fácil bater nesse limite durante os testes e receber o erro toomanyrequests. Fazer login evita isso desde o início.


Passo 2: Baixar as imagens manualmente antes do deploy

Este é o passo que mais gente pula — e é o que mais causa erro. O Docker Swarm não baixa a imagem sozinho de forma confiável ao subir uma stack; ele espera que ela já esteja no servidor. Se não estiver, todos os serviços ficam com status rejected e a mensagem No such image.

Baixe as quatro imagens do Dify manualmente, uma por vez:

docker pull langgenius/dify-api:1.13.3
docker pull langgenius/dify-web:1.13.3
docker pull langgenius/dify-sandbox:0.2.14
docker pull langgenius/dify-plugin-daemon:0.5.3-local

Espere cada uma terminar antes de seguir para a próxima. Se aparecer erro de rate limit aqui, volte ao Passo 1.

Nota sobre versões: as tags acima são as que testamos e confirmamos que funcionam juntas. Evite usar :latest — tags fixas garantem que a API, o Worker e o Plugin Daemon sejam sempre versões compatíveis entre si.


Passo 3: Criar os bancos de dados no PostgreSQL

O Dify precisa de dois bancos separados dentro do mesmo Postgres: um para a aplicação e outro exclusivo para o sistema de plugins.

Acesse o container do Postgres:

PG=$(docker ps -qf name=postgres)

Crie os dois bancos:

docker exec -i $PG psql -U postgres -c "CREATE DATABASE difydb;"
docker exec -i $PG psql -U postgres -c "CREATE DATABASE dify_plugin;"

Habilite a extensão vetorial no banco principal (necessária para embeddings e busca semântica):

docker exec -i $PG psql -U postgres -d difydb -c "CREATE EXTENSION IF NOT EXISTS vector;"

Por que dois bancos: o plugin_daemon (serviço que gerencia plugins e integrações) mantém seu próprio schema, separado da aplicação principal. Colocar tudo no mesmo banco causa conflito de migrações.


Passo 4: Criar os volumes externos

O Dify guarda arquivos de usuário e chaves de criptografia em volumes persistentes. Crie-os antes do deploy:

docker volume create dify_storage_data
docker volume create dify_plugin_storage

Passo 5: Gerar as chaves de segurança

Gere quatro chaves aleatórias, uma de cada vez:

openssl rand -base64 42

Guarde os quatro valores gerados — você vai colar cada um em um lugar específico da stack no próximo passo. Atenção: algumas dessas chaves precisam ser idênticas em dois pontos diferentes da stack (a própria stack indica isso nos comentários). Se os valores não baterem, a API e o Plugin Daemon não conseguem se autenticar entre si.


Passo 6: Colar e ajustar a stack no Portainer

Vá em Stacks → Add stack, dê um nome (ex: dify), e cole a stack abaixo no editor.

Antes de clicar em Deploy, revise três coisas:

  1. Domínio: troque todas as ocorrências de seudominio.com.br pelo subdomínio real que você vai usar (ex: dify.suaempresa.com.br). Isso aparece tanto nas variáveis de ambiente quanto nas labels do Traefik — troque em todos os lugares.
  2. Chaves: substitua os placeholders pelas chaves geradas no Passo 5, respeitando os pareamentos indicados nos comentários.
  3. Senha do Postgres: use a senha real do seu banco compartilhado.
# ============================================================
# COLE AQUI A SUA STACK FINAL
# ============================================================

Clique em Deploy the stack.


Passo 7: Corrigir a permissão do volume de storage

Este passo evita um erro que só aparece na hora de criar a conta de administrador (PermissionDenied: permission denied ao salvar as chaves de criptografia).

Descubra qual usuário o container da API usa internamente:

docker exec $(docker ps -qf name=dify_api) id

O resultado normalmente é uid=1001(dify) gid=1001(dify). Ajuste a permissão do volume para esse mesmo usuário:

docker volume inspect dify_storage_data

Copie o valor de "Mountpoint" da saída e rode (troque 1001 se o id retornar outro número):

chown -R 1001:1001 /var/lib/docker/volumes/dify_storage_data/_data

Force o serviço a reiniciar com a permissão corrigida:

docker service update --force dify_api
docker service update --force dify_worker

Por que isso acontece: o volume é criado vazio, geralmente com dono root. Mas o processo dentro do container roda com um usuário sem privilégios (dify, uid 1001) por segurança. Sem essa correção, a API não consegue gravar as chaves privadas de criptografia do primeiro workspace.


Passo 8: Acompanhar a migração do banco

Antes de acessar a tela de instalação, confira se a API terminou de criar as tabelas:

docker service logs dify_api --tail 50 -f

Espere aparecer a linha:

Database migration successful!

seguida de:

[INFO] Listening at: http://0.0.0.0:5001

Isso confirma que o backend está pronto. Se aparecer algum ERROR durante a migração (por exemplo Can't locate revision), o banco provavelmente foi tocado por uma tentativa anterior e precisa ser recriado do zero:

docker service scale dify_api=0
docker service scale dify_worker=0

docker exec -i $PG psql -U postgres -c "DROP DATABASE IF EXISTS difydb;"
docker exec -i $PG psql -U postgres -c "CREATE DATABASE difydb;"
docker exec -i $PG psql -U postgres -d difydb -c "CREATE EXTENSION IF NOT EXISTS vector;"

docker service scale dify_api=1
docker service scale dify_worker=1

Passo 9: Primeiro acesso e criação da conta de administrador

Acesse https://seudominio.com.br/install.

Se aparecer uma tela pedindo “Senha de inicialização do administrador”, significa que a variável INIT_PASSWORD da sua stack não está vazia. O valor mais simples é deixá-la em branco (INIT_PASSWORD: ""), o que faz o Dify pular direto para a criação da conta.

Na tela de configuração, preencha:

  • E-mail: use o mesmo que você configurará depois no SMTP
  • Nome de usuário
  • Senha: precisa ter letras, números e mais de 8 caracteres (ex: Empresa2026Admin)

Clique em Configuração. Se os Passos 3 e 7 foram feitos corretamente, a conta é criada sem erro e você cai direto no painel do Dify.


Resolução de problemas comuns

SintomaCausaSolução
Serviço fica em 0/1, status rejected, erro No such imageImagem não foi baixada no servidorRode os docker pull do Passo 2
toomanyrequests ao puxar imagemLimite de download anônimo do Docker Hubdocker login (Passo 1)
Página trava no spinner de carregamentoURL da API não bate com o domínio realRevise as variáveis de ambiente e labels do Traefik (Passo 6)
Bad Gateway ao acessar o domínioTraefik não sabe qual rede usar (serviço está em 2 redes)Adicione a label traefik.docker.network=traefik_public no serviço
Can't locate revision nos logs da APIBanco de dados foi tocado por uma tentativa anteriorRecrie o banco do zero (Passo 8)
PermissionDenied: permission denied ao criar conta adminVolume de storage com dono erradoAjuste o chown do volume (Passo 7)

Depois do deploy

Com a conta criada, você já pode configurar os provedores de LLM (OpenAI, Anthropic, etc.), criar seu primeiro workflow e conectar o Dify às suas automações — por exemplo, via webhook no n8n usando a Service API URL.

				
					version: "3.8"

# ==========================================================================
# DIFY - Stack para Portainer + Docker Swarm + Traefik
# Domínio: agentedify.seu_dominio.com.br
# --------------------------------------------------------------------------
# SEM nginx: o Traefik roteia direto para 'web' (frontend) e 'api' (backend),
# usando as regras de path que o Dify exige (/api, /v1, /console/api, /files, etc).
#
# PRÉ-REQUISITOS (rode via SSH UMA VEZ antes do deploy):
#   docker login                       # resolve o rate limit do Docker Hub
#   PG=$(docker ps -qf name=postgres)
#   docker exec -i $PG psql -U postgres -c "CREATE DATABASE difydb;"
#   docker exec -i $PG psql -U postgres -c "CREATE DATABASE dify_plugin;"
#   docker exec -i $PG psql -U postgres -d difydb -c "CREATE EXTENSION IF NOT EXISTS vector;"
#   docker volume create dify_storage_data
#   docker volume create dify_plugin_storage
#
# CHAVES: troque os placeholders __TROQUE__ por strings geradas com:
#   openssl rand -base64 42
# Respeite os pareamentos indicados nos comentários (mesmo valor nos dois lados).
#
# CLOUDFLARE: apontar o registro agentedify.seu_dominio.com.br para o IP do servidor
#   (proxy pode ficar ativo/laranja; o Traefik faz o SSL via Let's Encrypt).
# ==========================================================================

x-shared-env: &shared-env
  CONSOLE_API_URL: "https://agentedify.seu_dominio.com.br"
  CONSOLE_WEB_URL: "https://agentedify.seu_dominio.com.br"
  SERVICE_API_URL: "https://agentedify.seu_dominio.com.br"
  APP_API_URL: "https://agentedify.seu_dominio.com.br"
  APP_WEB_URL: "https://agentedify.seu_dominio.com.br"
  FILES_URL: "https://agentedify.seu_dominio.com.br"

  LOG_LEVEL: "INFO"
  SECRET_KEY: "API_KEY"
  INIT_PASSWORD: ""
  MIGRATION_ENABLED: "true"
  DEPLOY_ENV: "PRODUCTION"

  # Postgres compartilhado (stack pgvector já existente, hostname: postgres)
  DB_TYPE: "postgresql"
  DB_USERNAME: "postgres"
  DB_PASSWORD: "SUA_SENHA_POSTGRES"
  DB_HOST: "postgres"
  DB_PORT: "5432"
  DB_DATABASE: "difydb"

  # Redis compartilhado, sem senha
  REDIS_HOST: "redis"
  REDIS_PORT: "6379"
  REDIS_USERNAME: ""
  REDIS_PASSWORD: ""
  REDIS_USE_SSL: "false"
  REDIS_DB: "0"
  CELERY_BROKER_URL: "redis://redis:6379/1"

  WEB_API_CORS_ALLOW_ORIGINS: "*"
  CONSOLE_CORS_ALLOW_ORIGINS: "*"

  STORAGE_TYPE: "opendal"
  OPENDAL_SCHEME: "fs"
  OPENDAL_FS_ROOT: "storage"

  # Vetorial no mesmo Postgres (pgvector já instalado na imagem pgvector/pgvector:pg16)
  VECTOR_STORE: "pgvector"
  PGVECTOR_HOST: "postgres"
  PGVECTOR_PORT: "5432"
  PGVECTOR_USER: "postgres"
  PGVECTOR_PASSWORD: "SUA_SENHA_POSTGRES"
  PGVECTOR_DATABASE: "difydb"

  MAIL_TYPE: "smtp"
  MAIL_DEFAULT_SEND_FROM: "Dify Admin <contato@seu_dominio.com.br>"
  SMTP_SERVER: "smtp.mailgun.org"
  SMTP_PORT: "587"
  SMTP_USERNAME: "contato@seu_dominio.com.br"
  SMTP_PASSWORD: "API_KEY"
  SMTP_USE_TLS: "true"

  CODE_EXECUTION_ENDPOINT: "http://sandbox:8194"
  CODE_EXECUTION_API_KEY: "API_KEY"

  SSRF_PROXY_HTTP_URL: "http://ssrf_proxy:3128"
  SSRF_PROXY_HTTPS_URL: "http://ssrf_proxy:3128"

  PLUGIN_DAEMON_URL: "http://plugin_daemon:5002"
  PLUGIN_DAEMON_KEY: "API_KEY"
  INNER_API_KEY_FOR_PLUGIN: "API_KEY"
  PLUGIN_REMOTE_INSTALL_HOST: "plugin_daemon"
  PLUGIN_REMOTE_INSTALL_PORT: "5003"
  PLUGIN_MAX_PACKAGE_SIZE: "52428800"
  MARKETPLACE_API_URL: "https://marketplace.dify.ai"
  MARKETPLACE_URL: "https://marketplace.dify.ai"

services:

  # ======================= API (backend) =======================
  api:
    image: langgenius/dify-api:1.13.3
    environment:
      <<: *shared-env
      MODE: "api"
    volumes:
      - dify_storage_data:/app/api/storage
    networks:
      - traefik_public
      - digital_network
    depends_on:
      - plugin_daemon
    deploy:
      mode: replicated
      replicas: 1
      placement:
        constraints:
          - node.role == manager
      resources:
        limits:
          cpus: "1"
          memory: 2048M
      restart_policy:
        condition: on-failure
      labels:
        - traefik.enable=true
        # Diz ao Traefik para usar a rede traefik_public (o serviço está em 2 redes)
        - traefik.docker.network=traefik_public
        # Rotas de backend do Dify -> serviço api (porta 5001)
        - "traefik.http.routers.dify-api.rule=Host(`agentedify.seu_dominio.com.br`) && (PathPrefix(`/console/api`) || PathPrefix(`/api`) || PathPrefix(`/v1`) || PathPrefix(`/files`) || PathPrefix(`/e/`) || PathPrefix(`/mcp`))"
        - traefik.http.routers.dify-api.entrypoints=websecure
        - traefik.http.routers.dify-api.tls.certresolver=le
        - traefik.http.routers.dify-api.priority=10
        - traefik.http.routers.dify-api.service=dify-api
        - traefik.http.services.dify-api.loadbalancer.server.port=5001
        - traefik.http.services.dify-api.loadbalancer.passHostHeader=true

  # ======================= WORKER (Celery) =======================
  worker:
    image: langgenius/dify-api:1.13.3
    environment:
      <<: *shared-env
      MODE: "worker"
    volumes:
      - dify_storage_data:/app/api/storage
    networks:
      - digital_network
    deploy:
      mode: replicated
      replicas: 1
      placement:
        constraints:
          - node.role == manager
      resources:
        limits:
          cpus: "1"
          memory: 2048M
      restart_policy:
        condition: on-failure

  # ======================= WEB (frontend) =======================
  web:
    image: langgenius/dify-web:1.13.3
    environment:
      CONSOLE_API_URL: "https://agentedify.seu_dominio.com.br"
      APP_API_URL: "https://agentedify.seu_dominio.com.br"
      MARKETPLACE_API_URL: "https://marketplace.dify.ai"
      MARKETPLACE_URL: "https://marketplace.dify.ai"
    networks:
      - traefik_public
      - digital_network
    deploy:
      mode: replicated
      replicas: 1
      placement:
        constraints:
          - node.role == manager
      resources:
        limits:
          cpus: "1"
          memory: 1024M
      restart_policy:
        condition: on-failure
      labels:
        - traefik.enable=true
        # Diz ao Traefik para usar a rede traefik_public (o serviço está em 2 redes)
        - traefik.docker.network=traefik_public
        # Catch-all do domínio -> frontend (porta 3000). Prioridade menor que a api.
        - "traefik.http.routers.dify-web.rule=Host(`agentedify.seu_dominio.com.br`)"
        - traefik.http.routers.dify-web.entrypoints=websecure
        - traefik.http.routers.dify-web.tls.certresolver=le
        - traefik.http.routers.dify-web.priority=1
        - traefik.http.routers.dify-web.service=dify-web
        - traefik.http.services.dify-web.loadbalancer.server.port=3000
        - traefik.http.services.dify-web.loadbalancer.passHostHeader=true

  # ======================= PLUGIN DAEMON =======================
  plugin_daemon:
    image: langgenius/dify-plugin-daemon:0.5.3-local
    environment:
      DB_TYPE: "postgresql"
      DB_HOST: "postgres"
      DB_PORT: "5432"
      DB_USERNAME: "postgres"
      DB_PASSWORD: "SUA_SENHA_POSTGRES"
      DB_DATABASE: "dify_plugin"
      DB_SSL_MODE: "disable"

      REDIS_HOST: "redis"
      REDIS_PORT: "6379"
      REDIS_PASSWORD: ""

      SERVER_PORT: "5002"
      SERVER_KEY: "API_KEY"
      DIFY_INNER_API_URL: "http://api:5001"
      DIFY_INNER_API_KEY: "API_KEY"

      PLUGIN_REMOTE_INSTALLING_HOST: "0.0.0.0"
      PLUGIN_REMOTE_INSTALLING_PORT: "5003"
      PLUGIN_WORKING_PATH: "/app/storage/cwd"
      FORCE_VERIFYING_SIGNATURE: "true"

      PLUGIN_STORAGE_TYPE: "local"
      PLUGIN_STORAGE_LOCAL_ROOT: "/app/storage"
      PLUGIN_INSTALLED_PATH: "plugin"
      PLUGIN_PACKAGE_CACHE_PATH: "plugin_packages"
      PLUGIN_MEDIA_CACHE_PATH: "assets"

      MAX_PLUGIN_PACKAGE_SIZE: "52428800"
      PYTHON_ENV_INIT_TIMEOUT: "120"
      PLUGIN_MAX_EXECUTION_TIMEOUT: "600"
      PLUGIN_STDIO_BUFFER_SIZE: "1024"
      PLUGIN_STDIO_MAX_BUFFER_SIZE: "5242880"
    volumes:
      - dify_plugin_storage:/app/storage
    networks:
      - digital_network
    deploy:
      mode: replicated
      replicas: 1
      placement:
        constraints:
          - node.role == manager
      resources:
        limits:
          cpus: "0.5"
          memory: 1024M
      restart_policy:
        condition: on-failure

  # ======================= SANDBOX =======================
  sandbox:
    image: langgenius/dify-sandbox:0.2.14
    environment:
      API_KEY: "API_KEY"
      GIN_MODE: "release"
      WORKER_TIMEOUT: "15"
      ENABLE_NETWORK: "true"
      HTTP_PROXY: "http://ssrf_proxy:3128"
      HTTPS_PROXY: "http://ssrf_proxy:3128"
      SANDBOX_PORT: "8194"
    networks:
      - digital_network
    deploy:
      mode: replicated
      replicas: 1
      placement:
        constraints:
          - node.role == manager
      resources:
        limits:
          cpus: "0.5"
          memory: 512M
      restart_policy:
        condition: on-failure

  # ======================= SSRF PROXY =======================
  ssrf_proxy:
    image: ubuntu/squid:latest
    networks:
      - digital_network
    deploy:
      mode: replicated
      replicas: 1
      placement:
        constraints:
          - node.role == manager
      resources:
        limits:
          cpus: "0.2"
          memory: 256M
      restart_policy:
        condition: on-failure

volumes:
  dify_storage_data:
    external: true
  dify_plugin_storage:
    external: true

networks:
  traefik_public:
    external: true
  digital_network:
    external: true
				
			
				
					# ==========================================
      # CONFIGURAÇÕES AVANÇADAS DE ROTAS E ACESSO
      # ==========================================
      # REGRA GERAL PARA ALUNOS: Em instalações padrão (tudo no mesmo servidor), 
      # deixe estas variáveis em branco! O Dify é inteligente e calculará as rotas 
      # automaticamente com base no seu domínio principal (APP_WEB_URL). 
      # Só preencha se for separar o sistema em vários servidores diferentes.

      # Senha do primeiro Administrador. 
      # Recomendação: Deixe vazio. Assim, o Dify pedirá para você criar a senha 
      # na tela no seu primeiro acesso, evitando que ela vaze neste arquivo.
      - INIT_PASSWORD= # Ex: SenhaAdminForte2026!

      # URL onde o painel de criação de IA será acessado.
      # Útil apenas se a empresa quiser o painel em um domínio isolado (ex: admin.empresa.com)
      - CONSOLE_WEB_URL= # Ex: https://dify.suaempresa.com.br

      # Caminho da API interna do painel.
      # Preencher apenas se o frontend visual e o backend rodarem em máquinas diferentes.
      - CONSOLE_API_URL= # Ex: https://dify.suaempresa.com.br/console/api

      # A porta de entrada para integrações externas (onde seu n8n vai bater!).
      # Se vazio, ele assume seu domínio principal + a rota /v1.
      - SERVICE_API_URL= # Ex: https://dify.suaempresa.com.br/v1

      # Subdomínio exclusivo para carregar e exibir os PDFs e imagens (CDN).
      # Útil apenas para aliviar o servidor principal quando houver milhares de acessos.
      - FILES_URL= # Ex: https://arquivos.suaempresa.com.br
				
			

Passo 5: Criando o Banco de Dados no PostgreSQL

Para que o Dify consiga iniciar corretamente, o banco de dados principal já deve existir dentro do seu PostgreSQL. Vamos criá-lo via terminal.

  1. Acesse o console/terminal do contêiner do seu PostgreSQL.

  2. Certifique-se de acessar com o usuário postgres (e não como root).

  3. Abra a interface de comandos do banco digitando:

				
					psql
				
			

Crie o banco de dados rodando o comando SQL abaixo:

				
					CREATE DATABASE difydb;
				
			
Subindo-Stack-Portainer-Traefik

Conclusão: Após o Deploy

  • Aguarde o processamento: Após clicar em “Deploy the stack” no Portainer, aguarde de 5 a 10 minutos. O Dify possui vários serviços que precisam inicializar e criar as tabelas no banco de dados na primeira execução.

  • Primeiro Acesso: Ao acessar a URL configurada pela primeira vez, você precisará definir a conta de Administrador. Recomendamos usar o mesmo endereço de e-mail que você cadastrou no servidor SMTP na etapa de configuração. Guarde sua senha com segurança!

Menu Interativo

~/digitalinterativo/trilhas.sh — aulas
/
#!/usr/bin/trilha --interativa --self-hosted
🔓 aberto🔐 exclusivo aluno⏳ em breve
COMPARTILHAR