# 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.

URL: https://www.back4app.com/blog/pt/como-hospedar-um-app-flutter-web-com-docker
Publicado: 2026-10-26 · Testado: 2026-09-23
Testado em: Containers: Back4app, plano Free · Local: macOS, Flutter 3.47.5, Chromium · Imagem: estágio de build Debian bookworm, runtime nginx 1.27
Publisher: Back4app Engineering

`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](https://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`.

## 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](/blog/blog-assets/host-a-flutter-web-app-with-docker/01-web-hosting-paid.jpg)

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 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.

```dockerfile
# 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.

```nginx
# 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](/blog/blog-assets/host-a-flutter-web-app-with-docker/03-configure-deploy.jpg)

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](/blog/blog-assets/host-a-flutter-web-app-with-docker/05-deployment-ready.jpg)

## 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ção | Resultado |
|---|---|
| `GET /` | `200`, `server: nginx/1.27.5` |
| `GET /some-route-that-does-not-exist` | `200` — o fallback da SPA funciona |
| `index.html` | `cache-control: no-store, must-revalidate` |
| `flutter_bootstrap.js` | `cache-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](/blog/blog-assets/host-a-flutter-web-app-with-docker/04-app-live.jpg)

## 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.
