Referência

Documentação

Como o dPanel funciona por dentro e quais são exatamente as regras.

Arquitetura

O dPanel é um plugin WHM escrito em Perl, usando o interpretador que o próprio cPanel distribui. Não tem daemon, não tem banco, não tem dependência para instalar: o estado vive em arquivos sob /var/cpanel/dpanel/, todos pertencentes ao root.

O acesso é restrito a root pelo AppConfig do WHM, e cada requisição revalida essa permissão no próprio programa: quem não tiver privilégio total não passa.

Um único ponto de entrada O WHM se recusa a executar qualquer CGI que não esteja registrado no AppConfig, e o AppConfig aceita uma URL por plugin. Por isso a interface e a API convivem no mesmo arquivo: index.cgi entrega a página, e index.cgi?action=... responde JSON.

Nenhum comando passa por shell. Toda execução usa lista de argumentos, o que elimina a classe inteira de falhas de injeção via nome de container, domínio ou caminho.

Ciclo de vida do stack

EtapaO que acontece
Inspecionar lê o arquivo do cliente sem seguir symlink, conferindo dono e tamanho; mostra o original e a versão normalizada lado a lado
Publicar valida, normaliza e grava a cópia própria em /var/cpanel/dpanel/stacks/{conta}/; registra portas e caminhos
Subir reconfere os caminhos e o firewall, checa disco e roda docker compose up -d em segundo plano
Operar iniciar, parar, reiniciar, kill, ver log, por container ou pelo stack inteiro
Remover derruba os containers preservando volumes; com remoção completa, apaga também a configuração e o acesso remoto ao MySQL

O arquivo publicado é conferido por hash antes de cada subida. Se alguém editar a cópia do root por fora do painel, o dPanel recusa e pede uma republicação.

Validação do compose

Esse é o limite de segurança do produto: um arquivo escrito por usuário não confiável vai ser executado pelo root. A regra é lista de permissões: o que não está previsto é recusado, então uma chave nova do Docker nunca passa por engano.

Leitura do arquivo

  • Aberto sem seguir symlink, conferindo que é arquivo comum e que o dono é a conta.
  • Limite de 256 KB para o compose e 32 KB para o .env.
  • O YAML é interpretado por um processo separado, rodando como usuário sem privilégio e com teto de memória, CPU e tempo. Interpretar YAML hostil é a operação mais arriscada do fluxo, e ela acontece fora do root.
  • Recusa mais de um documento no arquivo, tags customizadas, aninhamento acima de 20 níveis e mais de 10 mil nós.

Chaves recusadas, sempre

ChaveMotivo
privilegedequivale a root no host
cap_addcapabilities extras em servidor compartilhado
network_modehost coloca o container fora do firewall
pid, ipc, uts, cgroup namespaces do host expõem processos e memória
devices, device_cgroup_rulesacesso a dispositivos do host
sysctlsajuste de parâmetros de kernel
volumes_from, links, external_links alcançam containers de outras contas
userns_mode, cgroup_parent, group_add burlam o isolamento e os limites
secrets, configs, label_file apontam para caminhos do host
include, extendscarregam outros arquivos do disco
deploychaves de swarm; os limites são definidos pelo dPanel
dns, dns_searcho Docker já resolve; evita exfiltração dirigida
security_opt que enfraqueça seccomp:unconfined e similares; só no-new-privileges é aceito

Outras restrições

  • Máximo de 8 serviços por stack e 4 portas publicadas por serviço.
  • Imagens só de registries conhecidos: Docker Hub, GHCR, Quay, GitLab, ECR público, Microsoft, LinuxServer, Kubernetes e GCR. Registry em porta não padrão é recusado.
  • Volumes nomeados não podem usar driver_opts: é bind mount disfarçado.
  • Redes não podem ser externas nem usar driver_opts; todas são substituídas por uma rede isolada por conta.
  • .env aceita só CHAVE=valor, sem quebra de linha no valor. A substituição de variáveis é feita pelo próprio dPanel, usando apenas esse arquivo, nunca o ambiente do root.
Validação depois da substituição As variáveis são resolvidas antes da checagem das regras. Assim image: ${IMG} não serve para esconder um valor proibido.

O que é injetado

O arquivo que vai para o Docker é sempre o normalizado pelo dPanel. Além de resolver caminhos e portas, ele acrescenta em cada serviço:

ItemValorPara quê
mem_limit / memswap_limit512m (teto 1024m)evita consumir a RAM do servidor
cpus0.5 (teto 1.0)evita monopolizar a CPU
pids_limit128 (teto 512)barra fork bomb
ulimitsnofile, nproc, coresegunda camada de contenção
shm_size64m (teto 128m)memória compartilhada conta como RAM
inittruerecolhe processos zumbis
security_optno-new-privilegesimpede escalar privilégio dentro do container
cap_drop / cap_addtudo, e devolve um conjunto mínimomenos capabilities que o padrão do Docker
loggingjson-file, 10m × 3log de container não enche o disco
extra_hostshost.docker.internalacesso ao MySQL do servidor
networksrede isolada da contacontainers de contas diferentes não se enxergam
restartunless-stoppedalways brigaria com o painel

container_name é removido: o nome passa a ser derivado do projeto, o que torna colisão entre contas impossível.

Portas

Você não escolhe a porta do host: o dPanel escolhe. Escreva só a porta interna:

ports:
  - "80"          # recomendado
  - "8080:80"     # aceito: usa 8080 se estiver livre na faixa
  • Faixa reservada padrão: 20000 a 29999.
  • Publicação sempre em 127.0.0.1. Se o arquivo pedir 0.0.0.0, o dPanel corrige e avisa.
  • A loopback também é filtrada. Publicar em 127.0.0.1 barra só quem vem de fora; a loopback é compartilhada por todas as contas do servidor. A cadeia DPANEL_LO deixa a porta aceitar apenas o nginx, o root e a própria conta dona do stack; o PHP de outra conta leva connection refused.
  • Uma porta pedida fora da faixa, ocupada no sistema ou pertencente a outra conta é trocada por uma livre, com aviso.
  • Só TCP, sem intervalos de porta.
  • As portas são estáveis: republicar um stack mantém a porta de cada serviço, justamente para não quebrar os redirecionamentos já configurados. Se ainda assim uma porta sumir, o painel avisa qual regra ficou órfã.

Volumes e caminhos

Bind mounts só podem apontar para dentro de /home/{conta}/. Caminhos relativos são resolvidos a partir da pasta docker da conta.

  • Recusa .. em qualquer posição, til, e caracteres de controle.
  • Resolve o caminho real e compara componente a componente: /home/bob nunca passa como se fosse da conta bo.
  • Cada componente do caminho precisa pertencer à conta ou ao root.
  • Diretórios reservados do cPanel são bloqueados: .ssh, .cpanel, mail, etc, logs, ssl, entre outros.
  • Montar o home inteiro não é permitido: amplo demais.
  • O caminho gravado no arquivo publicado é o caminho real resolvido, não o que o cliente escreveu.

Volumes nomeados são permitidos, mas não chegam ao Docker como volume. O dPanel materializa cada um como uma pasta em /home/{conta}/docker/volumes/{nome} e monta como bind. O compose do cliente continua sendo o padrão que ele copiou da internet; o que muda é onde o dado fica.

O motivo é que volume nomeado de verdade mora em /var/lib/docker/volumes/ — fora do /home, ou seja fora do backup do cPanel, fora da transferência da conta, e sobrevivendo à remoção dela como dado órfão. Dentro do home, some junto com a conta e entra no backup.

  • A pasta é criada com o uid da conta, nunca pelo root: se algum componente do caminho for um symlink para fora do home, a criação esbarra em permissão em vez de ser seguida.
  • Depois de criada, ela passa pela mesma resolução de caminho de qualquer bind, e o dev/inode é reconferido no deploy.
  • A seção volumes: some do arquivo publicado — não sobra volume vazio.
  • Quota: a quota do cPanel é contada por dono. Sem user: no serviço, o container grava como root e os arquivos não entram na quota da conta, mesmo estando no home. O painel avisa quando isso acontece.

Build com Dockerfile

Imagem construída na hora é permitida, com o contexto obrigatoriamente dentro do home da conta.

RegraLimite
Contextodentro de /home/{conta}/, resolvido pelo caminho real
Tamanhoaté 200 MB e 20.000 arquivos
Dockerfilerelativo ao contexto; sem ..
Imagens baseseguem a mesma lista de registries permitidos
Recusado# syntax= (baixa um interpretador externo), --mount=type=ssh, --mount=type=secret, --network=host
Chaves de build recusadasnetwork, secrets, ssh, extra_hosts, privileged, cache_from, additional_contexts

Se preferir desligar build por completo no seu servidor, mude allow_build na configuração.

Redirecionamentos

As regras são publicadas no nginx, que o ea-nginx coloca na frente do Apache. Cada conta e domínio recebe um arquivo no ponto de extensão oficial do ea-nginx, já incluído dentro do server{} daquele domínio e preservado por ea-nginx config:

/etc/nginx/conf.d/users/{conta}/{domínio}/dpanel.conf   as regras
/etc/nginx/conf.d/dpanel-maps.conf                     os maps (nível http)
/etc/nginx/conf.d/dpanel/proxy-params.conf             os cabeçalhos

O {conta}.conf gerado pelo cPanel nunca é tocado.

Por que não é mais VirtualHost do Apache No Apache prefork cada conexão proxiada segura um processo inteiro até fechar. Um punhado de túneis WebSocket estoura o MaxRequestWorkers e derruba todos os sites do servidor. No nginx a mesma conexão custa dois descritores num event loop. Foi essa mudança que motivou a v0.2.0, e por isso o ea-nginx passou a ser obrigatório.

Escopo: domínio inteiro ou só um caminho

  • Domínio inteiro: vira um location por expressão regular, porque um segundo location / no mesmo server{} faria o nginx recusar a configuração. Como regex vence prefixo simples, a lista de exclusão abaixo é obrigatória.
  • Só um caminho: vira location ^~ /caminho/, que vence qualquer regex. O prefixo é recortado antes de chegar ao container: /app/x chega como /x. Acessar /app sem barra devolve um 301 para /app/.

O que nunca é encaminhado

Mesmo com o domínio inteiro apontado para o container, estes caminhos continuam no site do cliente: /.well-known (senão o AutoSSL para de renovar certificado), /cpanel, /webmail, /webdisk, /autodiscover, /autoconfig, /cpcalendars, /cpcontacts, /mail/config e /Microsoft-Server-ActiveSync.

O que a sua aplicação recebe

Para o container, toda requisição parece vir do gateway do Docker (172.17.0.1 ou 172.18.0.1). Nunca use o endereço da conexão como IP do visitante; ele vem nos cabeçalhos:

CabeçalhoPara que serve
X-Real-IPIP do visitante. É este que substitui o REMOTE_ADDR.
X-Forwarded-ForCadeia de IPs; o primeiro é o visitante.
X-Forwarded-Protohttp ou https.
X-Forwarded-HostDomínio que o visitante digitou.
X-Forwarded-PortPorta pública original, 80 ou 443.
X-Forwarded-ServerIgual ao Host; mantido por compatibilidade com o formato do cPanel.
HostPreservado: a aplicação vê o domínio real.
CF-Connecting-IP
CF-Visitor
Só existem quando o domínio está atrás da Cloudflare.
Upgrade
Connection
Fazem o WebSocket funcionar. Não precisa tratar.

Em Express: app.set('trust proxy', true). Em PHP dentro do container: $_SERVER['HTTP_X_REAL_IP']. Para resposta em streaming (SSE), responda com X-Accel-Buffering: no; o nginx desliga o buffering só naquele endpoint.

As regras casam pelo Host exato Um vhost do cPanel costuma atender vários nomes ao mesmo tempo: domínios estacionados, mail., autodiscover., cpanel.. Encaminhar tudo quebraria webmail e a autoconfiguração de e-mail. O dPanel casa só o domínio escolhido mais a variante www; qualquer outro Host volta para o Apache.

Domínio adicional e estacionado

O vhost correto é descoberto a partir dos dados do próprio cPanel: domínio principal e subdomínio têm vhost próprio, adicional usa o subdomínio companheiro, estacionado usa o domínio em que está estacionado. Você escolhe o domínio na lista e o dPanel resolve o resto.

Aplicação segura

Antes de recarregar, o dPanel roda nginx -t. Se a configuração ficar inválida ele restaura os arquivos anteriores, testa de novo e informa o erro: nada entra em vigor quebrado. O reload do nginx é gracioso: nenhuma conexão em andamento cai. Includes de VirtualHost deixados por versões 0.1.x são removidos na primeira aplicação.

Isolamento de rede

Containers ficam presos por cadeias próprias de iptables. O ponto que a maioria das receitas erra: tráfego de container para o próprio host chega em INPUT, não em FORWARD. Regra em DOCKER-USER sozinha não protege nada disso.

FluxoPolítica
container → MySQL 3306 no gatewaypermitido
container → qualquer outra porta do hostrecusado
container → metadata da instância (169.254.0.0/16)recusado
container → 10/8, 172.16/12, 192.168/16recusado
container → internetpermitido
container → container da mesma contapermitido
host → containerpermitido
internet → porta publicadarecusado

As regras são reaplicadas em quatro momentos: quando o Docker inicia, depois de cada reconstrução do CSF, a cada 60 segundos por um timer do systemd, e antes de cada subida de stack: se não estiverem ativas, o deploy é recusado.

Para inspecionar ou reaplicar manualmente:

/usr/local/cpanel/whostmgr/docroot/cgi/dpanel/bin/dpanel-firewall status
/usr/local/cpanel/whostmgr/docroot/cgi/dpanel/bin/dpanel-firewall apply

As redes do Docker ficam fixas em 172.17.0.0/16 e 172.18.0.0/16, o que mantém previsível tanto o firewall quanto a liberação do MySQL.

Limites de recurso

Containers rodam como processos do root, fora do controle de recursos por conta do CloudLinux. O plano de hospedagem do cliente não limita o container dele. Por isso o controle é feito em três camadas:

  • Por serviço: memória, CPU e PIDs injetados no arquivo publicado.
  • Por stack e global: o dPanel soma o que já está publicado e recusa uma publicação que estoure o teto do servidor.
  • No kernel: um dpanel.slice do systemd limita todos os containers juntos. Mesmo com um erro de contabilidade no painel, o servidor não cai.

Vale dizer ao cliente: containers consomem de um pool administrado por você, não da conta dele.

Banco de dados

Dentro do container: host.docker.internal, porta 3306. É a única porta do servidor acessível.

Como o cliente vem de um IP de rede Docker e não de localhost, o dPanel libera o acesso remoto MySQL da conta para as faixas do Docker no primeiro deploy, usando a API do próprio cPanel, o que mantém o painel do cliente coerente. Ao remover o stack por completo, a liberação é desfeita.

Risco aceito Com 3306 alcançável das faixas do Docker, um container pode tentar autenticar contra o MySQL de qualquer conta. A barreira são as permissões por usuário e a força das senhas.

Arquivos no servidor

CaminhoConteúdo
/usr/local/cpanel/whostmgr/docroot/cgi/dpanel/o plugin
/var/cpanel/dpanel/stacks/{conta}/ compose normalizado, .env, original, metadados e log do deploy
/var/cpanel/dpanel/redirects.jsonregras de domínio
/var/cpanel/dpanel/ports.jsonportas atribuídas
/var/cpanel/dpanel/config.jsonajustes do servidor
/var/cpanel/dpanel/logs/actions.logauditoria em JSONL
/etc/docker/daemon.jsonconfiguração do daemon
/etc/systemd/system/dpanel.sliceteto global de recursos
/usr/local/csf/bin/csfpost.shreposição das regras após o CSF

Toda a árvore de estado é do root, sem permissão para mais ninguém.

Configuração

Ajustes do servidor ficam em /var/cpanel/dpanel/config.json. Só as chaves que você quiser mudar precisam estar lá; o resto usa o padrão.

{
  "port_min": 20000,
  "port_max": 29999,
  "mem_default": "512m",
  "mem_max": "1024m",
  "cpus_default": 0.5,
  "cpus_max": 1.0,
  "stack_mem_max": "1536m",
  "global_mem_max": "2048m",
  "max_services": 8,
  "allow_build": 1,
  "build_context_max_mb": 200,
  "min_free_disk_gb": 5,
  "registries": ["docker.io", "ghcr.io", "quay.io"]
}

Mudanças valem na próxima ação do painel, sem reiniciar nada. Se mexer no teto global, ajuste também o dpanel.slice, que é o limite real aplicado pelo kernel.

Auditoria

Cada ação vira uma linha JSON em /var/cpanel/dpanel/logs/actions.log, com data, usuário do WHM, IP de origem, conta afetada, resultado e detalhe. A aba Auditoria do painel mostra as últimas cem.

Eventos de segurança (recusa de validação, arquivo publicado alterado por fora, caminho montado que mudou) também vão para o syslog, para serem vistos por quem já monitora o servidor.

tail -f /var/cpanel/dpanel/logs/actions.log

A rotação é semanal, com 26 semanas de retenção.

Riscos conhecidos

Nenhum sistema é perfeito; estes são os pontos que ficaram em aberto e por quê.

RiscoSituação
Corrida de symlink O caminho real é resolvido na validação e reconferido, por identificador de arquivo, imediatamente antes de cada subida. Trocar o diretório por um link simbólico nessa janela de microssegundos é teoricamente possível. A troca é detectada e registrada; fechar de vez exige prender o arquivo por descritor, o que está planejado.
MySQL entre contas Qualquer container pode tentar autenticar contra o banco de qualquer conta. Permissões e senhas são a barreira.
Conteúdo da imagem O que a imagem faz por dentro é contido pelo isolamento, pelos limites e pelo firewall, mas o conteúdo dela não é auditado.
Espaço em disco Não há teto por conta para imagens. Há guarda de espaço livre antes do deploy, aviso no painel e limpeza manual.