Adicionando um Novo Serviço Docker à Infraestrutura¶
Uma das grandes vantagens da nossa arquitetura baseada em Docker, Portainer e Traefik é a facilidade com que podemos adicionar novos serviços e aplicações ao nosso servidor doméstico. Este guia fornece um checklist detalhado e um exemplo prático para integrar uma nova aplicação containerizada de forma segura e consistente com o restante da infraestrutura.
O processo geral envolve planejar o serviço, preparar os volumes e configurações (idealmente com Ansible), definir o serviço em um arquivo docker-compose.yml, e então implantá-lo via Portainer.
Checklist Detalhado para Adicionar um Novo Serviço Docker¶
Siga estes passos para garantir uma integração suave e bem-sucedida:
-
Fase de Planejamento e Pesquisa do Novo Serviço:
- Identifique a Imagem Docker:
- Qual imagem Docker você usará? É uma imagem oficial, uma da LinuxServer.io (lscr.io), do GitHub Container Registry, ou de outra fonte confiável?
- Verifique a popularidade da imagem, a frequência de atualizações, e se há problemas conhecidos (issues no GitHub do projeto).
- Escolha uma tag de versão específica (e.g.,
imagem:v1.2.3ouimagem:stable) em vez deimagem:latestpara garantir implantações mais previsíveis e evitar quebras inesperadas quandolatestfor atualizado.
- Requisitos de Recursos:
- Avalie o consumo estimado de CPU, RAM e espaço em disco do novo serviço.
- Ele se encaixa confortavelmente nos recursos da sua
core-services-vm? Ou, se for uma aplicação muito pesada ou com requisitos especiais (como a stack de IA), ela deveria ir para aai-desktop-vmou até mesmo uma nova VM dedicada? (Veja Gerenciando Recursos de VMs).
- Persistência de Dados (Volumes):
- O serviço precisa armazenar dados permanentemente (configurações, bancos de dados internos, arquivos de usuário)?
- Se sim, identifique quais diretórios dentro do container precisam ser mapeados para volumes persistentes. A documentação da imagem Docker geralmente especifica isso.
- Planeje onde esses volumes residirão no seu sistema de arquivos NFS. Uma boa prática é criar um subdiretório específico para o novo serviço dentro do seu dataset de volumes Docker:
{{ vm_nfs_mount_base_path }}/{{ zfs_docker_volumes_dataset_name }}/<nome_do_novo_servico>/config{{ vm_nfs_mount_base_path }}/{{ zfs_docker_volumes_dataset_name }}/<nome_do_novo_servico>/data(etc.)
- Configuração de Rede:
- Acesso Externo: O serviço precisa ser acessível da internet?
- Se sim, qual subdomínio você usará (e.g.,
novoservico.{{ base_domain }})?
- Se sim, qual subdomínio você usará (e.g.,
- Proteção com Authelia: Se for acessível externamente, ele deve ser protegido pelo Authelia? (Quase sempre sim, a menos que seja um serviço intencionalmente público como um blog).
- Comunicação Interna: O novo serviço precisa se comunicar com outros containers existentes (e.g., um banco de dados, um serviço de API)? Se sim, a quais redes Docker ele precisará ser conectado (
proxy,internal_services)?
- Acesso Externo: O serviço precisa ser acessível da internet?
- Variáveis de Ambiente:
- Quais variáveis de ambiente são obrigatórias ou recomendadas para configurar o serviço? Consulte a documentação da imagem Docker.
- Comuns incluem:
PUID,PGID(para permissões de arquivo),TZ(fuso horário), senhas, caminhos específicos dentro do container, opções de features.
- Portas Internas: Em qual porta o serviço escuta dentro do container? Isso é necessário para a configuração do Traefik.
- Identifique a Imagem Docker:
-
Preparação de Volumes e Arquivo
.env(Via Ansible - Recomendado): Para manter a consistência e automação (IaC):- No seu projeto
home-server/ansible/:- Crie um novo playbook Ansible, por exemplo,
ansible/playbooks/setup-novoservico-volumes.yml. - Este playbook deve ser direcionado à VM onde o novo serviço Docker rodará (e.g.,
core-services-vm). - Tarefas do Playbook:
- Usar o módulo
ansible.builtin.filepara criar os diretórios de volume necessários no caminho NFS (e.g.,{{ vm_nfs_mount_base_path }}/{{ zfs_docker_volumes_dataset_name }}/novoservico/config) com as permissões corretas (owner: {{ docker_puid }},group: {{ docker_pgid }},mode: '0775'ou0750). - Usar o módulo
ansible.builtin.templatepara gerar um arquivo.envpara o novo serviço. Este arquivo será colocado em{{ portainer_env_files_dest_path_on_vm }}/novoservico.envna VM.- Use o template
ansible/templates/portainer_stack_env.j2como base. - Adicione variáveis específicas para os caminhos de volume do novo serviço ao template ou ao playbook que chama o template, por exemplo:
# No playbook setup-novoservico-volumes.yml, ao chamar o template: # ... # template: # src: ../templates/portainer_stack_env.j2 # dest: "{{ portainer_env_files_dest_path_on_vm }}/novoservico.env" # vars: # stack_name: "novoservico" # Para lógica condicional no template # NOVOSERVICO_CONFIG_PATH: "{{ vm_nfs_mount_base_path }}/{{ zfs_docker_volumes_dataset_name }}/novoservico/config" # NOVOSERVICO_DATA_PATH: "{{ vm_nfs_mount_base_path }}/{{ zfs_docker_volumes_dataset_name }}/novoservico/data"
- Use o template
- Usar o módulo
- Crie um novo playbook Ansible, por exemplo,
- Execute o novo playbook Ansible:
Isso garante que os diretórios NFS e o arquivo
.envcom os caminhos corretos estejam prontos na VM antes de você tentar implantar a stack no Portainer.
- No seu projeto
-
Escrever o Arquivo
docker-compose.ymlpara a Nova Stack:- No seu projeto
home-server/, crie um novo diretório para a stack, por exemplo,docker/stacks/novoservico/. - Dentro dele, crie o arquivo
docker-compose.yml. - Estrutura Típica (Exemplo):
# docker/stacks/novoservico/docker-compose.yml version: '3.9' # Ou a versão mais recente suportada pelo seu Docker Compose networks: proxy: # Necessário se o serviço for exposto via Traefik external: true # Assumindo que a rede 'proxy' já foi criada pelo Ansible internal_services: # Se o serviço precisar se comunicar com outros backends (e.g., DBs) external: true # Assumindo que 'internal_services' também já existe services: novoservico: # Nome lógico do serviço dentro deste compose image: registry/nome_da_imagem:tag_especifica container_name: nome_do_container_novoservico # Nome do container no Docker restart: unless-stopped networks: # Conecte às redes necessárias - proxy # Se exposto via Traefik # - internal_services # Se comunica com outros serviços internos volumes: # Use as variáveis de ambiente que serão definidas no arquivo .env carregado pelo Portainer - "${NOVOSERVICO_CONFIG_PATH}:/config" # Mapeia o volume de configuração do serviço - "${NOVOSERVICO_DATA_PATH}:/data" # Exemplo de volume de dados # Adicione outros volumes conforme a documentação da imagem environment: - PUID=${DOCKER_PUID} # Do arquivo .env (valor do Ansible Vault) - PGID=${DOCKER_PGID} # Do arquivo .env (valor do Ansible Vault) - TZ=${SYSTEM_TIMEZONE} # Do arquivo .env (valor do Ansible Vault) # - OUTRA_VARIAVEL_CONFIG=${VALOR_DA_VARIAVEL} # Pode vir do .env ou definida no Portainer # - SENHA_ESPECIFICA_APP=${SENHA_DO_NOVOSERVICO_APP} # Esta será definida como secret no Portainer labels: # Labels para Traefik (apenas se o serviço for exposto externamente) # --- Labels Traefik --- traefik.enable: "true" # Habilita o Traefik para este serviço # Roteador HTTP traefik.http.routers.novoservico.rule: "Host(`novoservico.{{ base_domain }}`)" # Substitua {{ base_domain }} pelo seu domínio real traefik.http.routers.novoservico.entrypoints: "websecure" # Usa o entrypoint HTTPS (porta 443) traefik.http.routers.novoservico.tls.certresolver: "letsencrypt" # Usa Let's Encrypt para SSL # Middlewares (e.g., Authelia para proteção, headers de segurança) # Se precisar de múltiplos middlewares, separe por vírgula: "authelia@docker,securityHeaders@file" traefik.http.routers.novoservico.middlewares: "authelia@docker" # Se for proteger com Authelia # Serviço (como Traefik deve se conectar ao container internamente) traefik.http.services.novoservico.loadbalancer.server.port: "8000" # Porta INTERNA que o container 'novoservico' escuta # traefik.http.services.novoservico.loadbalancer.server.scheme: "http" # Se o container escuta em HTTP (padrão)
Nomenclatura Consistente para Labels Traefik
Use nomes consistentes e descritivos para seus roteadores e serviços Traefik (e.g.,
traefik.http.routers.nomedoservico...). O placeholder{{ base_domain }}nas labels será substituído pelo valor real se o container Traefik tiver acesso a essa variável (o que ele tem, viaBASE_DOMAIN_FROM_VAULTpassada para ele). - No seu projeto
-
Deploy da Nova Stack via Portainer: Siga o processo detalhado na Seção 6.2: Implantação de Stacks Docker (Processo Geral):
- Acesse o Portainer, selecione o endpoint Docker correto (e.g.,
localparacore-services-vm). - Vá para "Stacks" -> "+ Add stack".
- Nome: Dê um nome para a stack (e.g.,
novoservico-stack). - Build method: "Web editor". Cole o conteúdo do seu
docker-stacks/novoservico/docker-compose.yml. - Environment variables (Advanced mode):
- Load variables from .env file: Forneça o caminho DENTRO DA VM para o arquivo
.envque foi preparado pelo Ansible (e.g.,/opt/portainer_stack_envs/novoservico.env). - Adicionar Secrets Manualmente: Para quaisquer senhas ou tokens de API (e.g.,
SENHA_DO_NOVOSERVICO_APPse referenciada no compose), adicione-os aqui como variáveis de ambiente. NÃO coloque segredos diretamente nodocker-compose.ymlou no arquivo.envversionado.
- Load variables from .env file: Forneça o caminho DENTRO DA VM para o arquivo
- Clique em "Deploy the stack".
- Acesse o Portainer, selecione o endpoint Docker correto (e.g.,
-
Configuração de DNS na Cloudflare (se o serviço for exposto externamente):
- Se você usa uma Ingress Rule wildcard (
*.{{ base_domain }}) no seu Cloudflare Tunnel que aponta para o Traefik (conforme recomendado na Seção 5.5), este passo pode não ser estritamente necessário, pois o novo subdomínio (novoservico.{{ base_domain }}) já será coberto e roteado para o Traefik. - Caso contrário, ou se você quiser uma entrada explícita, adicione manualmente um registro CNAME no painel DNS da Cloudflare:
- Type:
CNAME - Name:
novoservico(o subdomínio do seu novo serviço, sem o domínio base). - Target: O hostname do seu Cloudflare Tunnel (e.g.,
SEU_TUNNEL_UUID.cfargotunnel.com). - Proxy Status: Certifique-se de que está Laranja (Proxied).
- Type:
- Aguarde alguns minutos para a propagação do DNS.
- Se você usa uma Ingress Rule wildcard (
-
Teste e Pós-Configuração:
- Acesse
https://novoservico.{{ base_domain }}no seu navegador. - Se o serviço estiver protegido por Authelia, você será redirecionado para o portal de login do Authelia.
- Verifique os logs do novo container no Portainer para quaisquer erros de inicialização ou operacionais.
- Realize qualquer configuração inicial necessária dentro da interface do próprio novo serviço (e.g., criar um usuário administrador, configurar preferências).
- Acesse
Exemplo Prático: Adicionando FileBrowser (Revisitado)¶
FileBrowser é um gerenciador de arquivos web simples e eficaz. Vamos revisitar o exemplo, focando no fluxo completo.
-
Planejamento:
- Imagem:
filebrowser/filebrowser:v2.29.0(ou mais recente estável). - Recursos: Leve, roda bem na
core-services-vm. - Persistência:
- Configuração:
/configdentro do container. Mapear para{{ vm_nfs_mount_base_path }}/{{ zfs_docker_volumes_dataset_name }}/filebrowser/config. - Dados (arquivos a serem gerenciados):
/srvdentro do container. Mapearemos para um diretório no NFS, e.g.,{{ vm_nfs_mount_base_path }}/{{ zfs_docker_volumes_dataset_name }}/filebrowser/shared_filesou até mesmo para{{ vm_nfs_mount_base_path }}/{{ zfs_media_dataset_name }}/se quisermos que ele navegue na nossa biblioteca de mídia.
- Configuração:
- Rede: Expor em
files.{{ base_domain }}, proteger com Authelia. Conectar à redeproxy. - Variáveis: PUID, PGID, TZ. FileBrowser usa a porta 80 internamente por padrão.
- Imagem:
-
Preparação Ansible (
setup-filebrowser-volumes.yml):- Crie o playbook.
- Tasks para criar os diretórios NFS:
{{ vm_nfs_mount_base_path }}/{{ zfs_docker_volumes_dataset_name }}/filebrowser/config{{ vm_nfs_mount_base_path }}/{{ zfs_docker_volumes_dataset_name }}/filebrowser/shared_files(exemplo para dados)
- Task para gerar
/opt/portainer_stack_envs/filebrowser.envnacore-services-vmcom:# /opt/portainer_stack_envs/filebrowser.env (Gerado por Ansible) DOCKER_PUID={{ docker_puid }} DOCKER_PGID={{ docker_pgid }} SYSTEM_TIMEZONE={{ system_timezone }} # Note: BASE_DOMAIN não é estritamente necessário aqui se as labels Traefik usam {{ base_domain }} # que o Traefik resolve, mas pode ser útil para consistência. BASE_DOMAIN={{ base_domain }} CORE_SERVICES_VM_IP_VAR={{ core_services_vm_ip_var }} # Se precisar para algo específico FILEBROWSER_CONFIG_PATH={{ vm_nfs_mount_base_path }}/{{ zfs_docker_volumes_dataset_name }}/filebrowser/config FILEBROWSER_SHARED_DATA_PATH={{ vm_nfs_mount_base_path }}/{{ zfs_docker_volumes_dataset_name }}/filebrowser/shared_files - Execute o playbook:
ansible-playbook ansible/playbooks/setup-filebrowser-volumes.yml --ask-vault-pass.
-
docker-compose.ymlpara FileBrowser (docker/stacks/filebrowser/docker-compose.yml):version: '3.9' networks: { proxy: { external: true } } services: filebrowser: image: filebrowser/filebrowser:v2.29.0 # Verifique a tag mais recente e estável container_name: filebrowser_app restart: unless-stopped # user: "${DOCKER_PUID}:${DOCKER_PGID}" # Filebrowser geralmente roda como root e gerencia permissões internas. # Se usar PUID/PGID, garanta que ele tenha acesso aos volumes. # Para simplicidade inicial, pode-se omitir, mas teste as permissões. networks: [proxy] volumes: - "${FILEBROWSER_CONFIG_PATH}:/config" # Configuração do FileBrowser (inclui filebrowser.db) - "${FILEBROWSER_SHARED_DATA_PATH}:/srv" # Diretório raiz que o FileBrowser servirá environment: - PUID=${DOCKER_PUID} # Para o processo interno se a imagem suportar - PGID=${DOCKER_PGID} - TZ=${SYSTEM_TIMEZONE} - FB_BASEURL=/ # Se FileBrowser estiver na raiz do subdomínio # FB_ADDRESS=0.0.0.0 # Padrão, escuta em todas as interfaces dentro do container # FB_PORT=80 # Porta interna padrão do container FileBrowser labels: traefik.enable: "true" traefik.http.routers.filebrowser.rule: "Host(`files.{{ base_domain }}`)" traefik.http.routers.filebrowser.entrypoints: "websecure" traefik.http.routers.filebrowser.tls.certresolver: "letsencrypt" traefik.http.routers.filebrowser.middlewares: "authelia@docker" # Protegido por Authelia traefik.http.services.filebrowser.loadbalancer.server.port: "80" # FileBrowser escuta na porta 80 internamente -
Deploy via Portainer:
- Endpoint:
local. - Stack Name:
filebrowser-stack. - Cole o compose.
- Carregue
/opt/portainer_stack_envs/filebrowser.env. - Não há secrets adicionais para FileBrowser por padrão.
- "Deploy the stack".
- Endpoint:
-
DNS Cloudflare:
- Se o wildcard
*.{{ base_domain }}já estiver configurado no seu Cloudflare Tunnel apontando para Traefik, nenhuma ação de DNS é necessária. - Caso contrário, adicione CNAME
filespara{{ base_domain }}apontando para seu túnel.
- Se o wildcard
-
Teste e Pós-Configuração:
- Acesse
https://files.{{ base_domain }}. - Autentique com Authelia.
- Você verá a tela de login do FileBrowser. O login padrão é
admin/admin. -
MUDE A SENHA PADRÃO DO FILEBROWSER!
Após o primeiro login no FileBrowser, vá para "Settings" -> "User Management" e altere a senha do usuárioadminimediatamente! - Configure usuários adicionais, permissões e outras preferências dentro do FileBrowser conforme necessário.
- Acesse
Seguindo este processo estruturado, você pode expandir seu servidor doméstico com uma vasta gama de aplicações e serviços auto-hospedados, mantendo a organização e a segurança.