Como hospedar um app Flutter web com Docker, sem administrar um servidor

O Flutter gera uma pasta de arquivos estáticos; o difícil é onde ela vai morar. Um Dockerfile de dois estágios e um repositório GitHub a colocam numa URL, no plano Free, sem servidor para administrar.

O app Flutter no ar num navegador: um título Cups counted acima de um zero, um botão Pour another e a linha de carimbo de build que o app imprime

Tradução do original em inglês, testado em 23 de setembro de 2026. Código, números e datas são os do original. Read in English

flutter build web entrega uma pasta. Essa é toda a saída: um index.html, uma pilha de JavaScript com hash, algumas fontes e ícones. Nada ali diz onde isso deve morar.

Essa última parte é o trabalho. A pasta precisa de um servidor web que entenda roteamento no cliente, de uma máquina para rodá-lo, de uma URL e de um jeito de levar um build novo para lá quando você muda algo — e nada disso é tarefa do Flutter.

Este guia faz tudo isso de uma vez: um Dockerfile de dois estágios que compila o build e o serve com nginx, um repositório GitHub e um deploy no plano Free. Tudo abaixo foi medido em 23 de setembro de 2026.

Pegue o código funcionando: o repositório companion está em github.com/templates-back4app/flutter-web-on-back4app — o app, o Dockerfile, a configuração do nginx e dois testes de widget que passam. Faça um fork e você consegue fazer o deploy sem escrever nada.

Prefere assistir ao método antigo?

O tutorial em vídeo da própria Back4app, de 2021, mostra um caminho diferente: ativar um subdomínio de hospedagem na dashboard e então enviar o build com a ferramenta de linha de comando b4a.

Video: Flutter Web Hosting
Watch it instead Flutter Web Hosting
Do canal da Back4app, dezembro de 2021. O método que ele mostra hoje exige um plano pago — a próxima seção explica o que mudou. Open on YouTube →

O caminho antigo de hospedagem mudou?

Sim, de duas maneiras que importam antes de você começar. O caminho no menu mudou de lugar, e o recurso saiu do plano Free.

No vídeo, a hospedagem web fica em App Settings → Server Settings → Web Hosting and Custom Domains. Hoje é um link Configure na Overview do app que abre Domain Settings, e num app Free essa página traz uma única mensagem: “Please upgrade your plan to activate your web hosting.”

A página Domain Settings de um app Free, mostrando Web Hosting com um aviso de upgrade no lugar do campo de subdomínio

O caminho gratuito hoje é a entrada Web Deployment na barra lateral do app, que abre o Back4app Containers e pede um repositório GitHub ou uma imagem do Docker Hub. É o caminho que este guia segue.

O tutorial de 2021 envia uma pasta. O de 2026 entrega um repositório e deixa a plataforma construir a imagem. O que você escreve muda na mesma medida: um Dockerfile, não um passo de cópia.

O que vai no Dockerfile?

Dois estágios. O primeiro compila o build do Flutter; o segundo o serve. Mantê-los separados é o que impede um SDK de 4 GB de pegar carona na imagem que roda o seu site.

# Stack: Flutter 3.47.5 (web) + nginx 1.27 | File: Dockerfile
FROM debian:bookworm-slim AS build
ARG FLUTTER_VERSION=3.47.5
ENV PATH="/opt/flutter/bin:${PATH}"
RUN apt-get update && apt-get install -y --no-install-recommends \
      curl ca-certificates git unzip xz-utils \
 && rm -rf /var/lib/apt/lists/*
RUN curl -fsSL "https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_${FLUTTER_VERSION}-stable.tar.xz" \
      -o /tmp/flutter.tar.xz \
 && tar xf /tmp/flutter.tar.xz -C /opt \
 && git config --global --add safe.directory /opt/flutter
WORKDIR /app
COPY pubspec.yaml pubspec.lock ./
RUN flutter pub get
COPY . .
RUN flutter build web --release --dart-define=BUILD_STAMP="$(date -u +%Y-%m-%dT%H:%M:%SZ)"

FROM nginx:1.27-alpine
COPY --from=build /app/build/web /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80

O SDK é fixado numa versão exata e baixado do próprio armazenamento de releases do Google, em vez de uma imagem da comunidade. Imagens Flutter pré-construídas existem, mas a que a maioria dos guias usa estava quatro versões minor atrás da stable no dia em que testamos, o que silenciosamente significa que seu container compila com um Flutter diferente do seu laptop.

Copiar o pubspec.yaml antes do resto do código-fonte é a única otimização que vale a pena manter: a resolução de dependências vira uma camada em cache, e editar lib/ não a repete.

O que o nginx precisa saber?

Que suas rotas não são arquivos. O Flutter resolve caminhos no navegador, então uma requisição para /settings não tem nada por trás no disco, e um nginx com configuração padrão devolve 404 para todo deep link e todo refresh.

# Stack: nginx 1.27 | File: nginx.conf
server {
  listen 80;
  root /usr/share/nginx/html;
  index index.html;

  location / {
    try_files $uri $uri/ /index.html;
  }

  location = /index.html {
    add_header Cache-Control "no-store, must-revalidate";
  }

  location ~* \.(js|wasm|json|png|svg|ttf|otf|woff2?)$ {
    expires 30d;
    add_header Cache-Control "public, immutable";
  }
}

As duas regras de cache formam um par. A saída do Flutter carrega hashes de conteúdo nos nomes dos arquivos, então esses arquivos podem ser cacheados com força e por muito tempo; o index.html é o único arquivo cujo nome nunca muda, e cacheá-lo é a receita para um deploy sair e ninguém ver.

Como fazer o deploy?

Faça o push do projeto para o GitHub e aponte o Back4app Containers para o repositório. Na barra lateral do app, Web Deployment → Deploy a Web App → GitHub repository.

Escolha o repositório, deixe o branch e o diretório raiz como estão, e a etapa de configuração se preenche sozinha. A nossa detectou o Dockerfile e definiu o runtime a partir dele, com a porta em auto: nada para digitar.

A etapa de configuração do deploy com o método de build detectado como Dockerfile, runtime vindo do Dockerfile, porta em auto e o plano Free a custo zero

Depois, espere. O primeiro build não tem camadas em cache, então ele baixa o SDK e compila do zero — o nosso levou 9 minutos e 13 segundos e terminou em Ready, com o app marcado como Available numa URL b4a.run.

A página Deployments mostrando o container disponível numa URL b4a.run, um deployment marcado como Ready em 9 minutos e 13 segundos, e um aviso de que a URL gratuita fica no ar por 60 minutos

Funcionou de verdade?

Confira os headers, não só a página. Um app Flutter renderiza num canvas, então “parece certo” diz menos do que o normal, e as regras do nginx são a parte com mais chance de estar errada.

VerificaçãoResultado
GET /200, server: nginx/1.27.5
GET /some-route-that-does-not-exist200 — o fallback da SPA funciona
index.htmlcache-control: no-store, must-revalidate
flutter_bootstrap.jscache-control: max-age=2592000, public, immutable

Como medimos: uma rodada em 23 de setembro de 2026, curl -sI contra a URL do deploy logo depois que o deploy reportou Ready; a duração do build é o número que a página Deployments mostra para aquele deployment.

O app Flutter no ar num navegador, mostrando o contador e uma linha com o carimbo do build

O que o plano Free não faz?

Três coisas que vale saber antes de planejar em cima dele, todas declaradas na dashboard, não inferidas.

A URL dura uma hora. A barra lateral diz “URL is temporary and will be live for 60 minutes”, ao lado de um botão de upgrade. É o bastante para conferir o seu trabalho, e não o bastante para mandar a um cliente.

Autodeploy no push é pago. Build and deploy diz isso onde a opção estaria, então no plano Free todo build novo é uma ação manual na dashboard.

Não existe campo para argumentos de build do Docker. Build and deploy oferece o método de build, o branch, o diretório raiz e a porta — nada mais. Variáveis de ambiente existem, mas são valores de runtime entregues ao container em execução, não ao build.

Quando um container é o lugar errado para um build Flutter?

Quando a saída é genuinamente estática e nada mais. Uma pasta de arquivos não precisa de um runtime de container, e um host estático comum ou um bucket com CDN vai servi-la com menos coisas para dar errado e sem cold start.

Quando você precisa que a URL seja permanente e gratuita. Este caminho dá uma URL permanente num plano pago; o plano Free serve para conferir que o build funciona.

E quando o seu app precisa de um backend de qualquer forma. Se o app Flutter fala com um banco de dados, as decisões interessantes estão desse lado, e onde os arquivos estáticos ficam é a metade menor do problema.

O que fazer em seguida?

Faça um fork do repositório e o deploy uma vez, antes de apontar o fluxo para o seu próprio app. É uma passada pela dashboard sem digitar nada, e ela diz se o GitHub App, o plano e o build se comportam na sua conta.

Depois troque pelo seu próprio lib/. O Dockerfile e a configuração do nginx não mudam — essa é a razão de colocá-los no repositório e não num campo da dashboard.

O hábito que vale manter é a conferência dos headers. Um deploy de Flutter web falha em silêncio: a página carrega, as rotas só quebram no refresh, e um index.html em cache esconde seu build mais novo pelo tempo que o navegador quiser. Quatro chamadas curl -sI pegam os três.

Tagsflutterflutter-webdockernginxcontainersweb-deploymentgithub

Perguntas frequentes

Como hospedar um app Flutter web?

Compile com flutter build web --release, que produz uma pasta de arquivos estáticos, e sirva essa pasta de qualquer servidor web. O caminho testado aqui coloca o nginx e o build numa imagem de container descrita por um Dockerfile, e faz o deploy a partir de um repositório GitHub no Back4app Containers. Em 23 de setembro de 2026 a compilação local levou 17 segundos e o build do container na plataforma levou 9 minutos e 13 segundos.

A hospedagem web da Back4app ainda é gratuita para um build Flutter?

Não o recurso que os tutoriais antigos usam. App Settings agora tem uma página Domain Settings para Web Hosting, e no plano Free ela diz: Please upgrade your plan to activate your web hosting. O caminho gratuito hoje é o Web Deployment, que abre o Back4app Containers e faz o deploy a partir de um repositório GitHub ou de uma imagem do Docker Hub.

Por que um app Flutter web precisa do nginx e não só de um host estático?

Porque as rotas do Flutter são resolvidas no cliente. Peça /settings diretamente e não existe esse arquivo no disco, então o servidor precisa cair no index.html e deixar o Flutter resolver a rota. A configuração do nginx no repositório companion faz isso com try_files, e também impede que o index.html seja cacheado enquanto deixa a saída do build com hash em cache por um mês.

Quanto tempo dura a URL gratuita do container?

Sessenta minutos. A dashboard diz com todas as letras: URL is temporary and will be live for 60 minutes, com um botão de upgrade ao lado. Planeje uma demo em torno disso, ou passe para um plano pago antes de compartilhar o link com alguém.

O build do container consegue pegar um valor que eu defino na dashboard?

Não como argumento de build do Docker. Build and deploy expõe o método de build, o branch, o diretório raiz e a porta, e variáveis de ambiente são valores de runtime que chegam ao container em execução, não ao build. Tudo o que o build precisa tem de vir do próprio Dockerfile, e é por isso que o carimbo de build no repositório companion é calculado dentro do passo RUN.

Revisado e verificado por Back4app Engineering, Editorial review na Back4app

Testado em 23 de setembro de 2026 — Containers: Back4app, plano Free · Local: macOS, Flutter 3.47.5, Chromium · Imagem: estágio de build Debian bookworm, runtime nginx 1.27.

Mais da Back4app Engineering

Containers · tutorial

Como hospedar um site estático no Back4app Containers com Docker e nginx

Containers · tutorial

Como fazer o deploy de um app Node.js com Docker e banco de dados, sem administrar um servidor

Containers · tutorial

Como fazer o deploy de FastAPI com Docker e banco de dados, sem administrar um servidor