# Primeiro container Docker em produção no Amazon ECS, passo a passo

> Do Dockerfile local ao container no Amazon ECS: imagem no Amazon ECR, cluster com EC2, task definition, service e deploy de uma versão nova.

- Autor: Henrylle Maia, consultor AWS
- Publicado em: 2026-10-14
- Assuntos: Containers
- URL: https://pages.formacaoaws.com.br/blog/primeiro-container-em-producao-no-ecs/
- Aula de origem: "Rodando e fazendo deploy do seu primeiro container docker em produção com o Amazon ECS do zero" (https://www.youtube.com/watch?v=me9SYyFoya8)

No fim deste texto, um container Docker que roda na sua máquina passa a rodar no Amazon ECS: imagem no Amazon ECR, cluster com uma instância EC2, task definition e um service que mantém a task de pé. Você derruba a aplicação de propósito para ver o ECS repor tudo sozinho e publica uma versão nova.

A aplicação é bem simples, um Nginx servindo um HTML. Gravei isto em 2022 e o console da AWS mudou desde então: as telas podem estar diferentes, mas a ordem dos passos e os conceitos valem.

## O que o ECS faz por você

O Amazon ECS é o serviço de gerenciamento de containers da AWS, o principal dela com essa finalidade. Rodar o container numa EC2 na mão parece mais prático, até você pensar no que envolve manter isso disponível. O ECS automatiza a **atualização** (quando você conecta esse processo a ele, ele atualiza os containers) e a **disponibilidade** (você diz quantas tasks quer rodando e ele faz o máximo para manter essa quantidade, subindo outra EC2 para o cluster se precisar).

São quatro peças. O **cluster** concentra o poder computacional, as instâncias EC2. A **task definition** descreve o container e diz qual imagem usar. O **service**, ligado à task definition, lança a **task**, que é o container rodando. E a imagem precisa estar num registry: poderia ser o Docker Hub, usei o Amazon ECR, o da própria AWS.

**Fluxo:** Máquina local (imagem gerada pelo Dockerfile) → Amazon ECR (repositório da imagem) → Cluster ECS (uma instância EC2) → Service (mantém 1 task rodando) → Task (o container Nginx na porta 80)

## Passo 1: rodar o container na sua máquina

Você precisa de Git e VS Code, no Windows ou no Linux (eu estava no Linux). O projeto tem uma pasta `meu_html` com um `hello.htm` e um Dockerfile só para o Nginx, o `Dockerfile-nginx`. Ele pega o que está em `meu_html` e copia para a pasta que o Nginx serve:

```dockerfile
FROM nginx
COPY meu_html /usr/share/nginx/html
```

![O projeto: a pasta meu_html com o hello.htm e o Dockerfile-nginx, que copia meu_html para /usr/share/nginx/html](https://pages.formacaoaws.com.br/blog/assets/posts/primeiro-container-em-producao-no-ecs/container-local.webp)

Gere a imagem passando o Dockerfile com `-f`, a tag `meu-nginx` e o diretório atual como contexto:

```bash
docker build -f Dockerfile-nginx -t meu-nginx .
```

Rode o container em modo detached, com a porta local 8081 apontando para a 80 do container, que é a porta padrão do Nginx, e confira se ele subiu:

```bash
docker run -d -p 8081:80 meu-nginx
docker ps
```

Abra `localhost:8081/hello.htm` no navegador. Na minha apareceu a mensagem "saí da máquina local e fui para o container". O objetivo agora é tirar esse container da máquina e rodar na AWS, gerenciado pelo ECS.

## Passo 2: subir a imagem para o Amazon ECR

No ECR, crie um repositório com o nome da imagem, `meu-nginx`. A tela do repositório tem os **push commands**, os comandos para subir a imagem. Copiei para um arquivo `build.sh` (no Windows, pode ser `build.bat`) e fiz uma adaptação: incluir no build o `-f` com o Dockerfile do Nginx. Com o ID da sua conta no lugar de `<id-da-conta>`, o script fica assim:

```bash
aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin <id-da-conta>.dkr.ecr.us-east-1.amazonaws.com
docker build -t meu-nginx -f Dockerfile-nginx .
docker tag meu-nginx:latest <id-da-conta>.dkr.ecr.us-east-1.amazonaws.com/meu-nginx:latest
docker push <id-da-conta>.dkr.ecr.us-east-1.amazonaws.com/meu-nginx:latest
```

A primeira linha pega o login no ECR pelo AWS CLI. A segunda gera a imagem, a terceira aplica a tag com o endereço do repositório e a quarta dá o push.

Antes de rodar, o AWS CLI precisa de uma credencial IAM. Criei um usuário com acesso programático e anexei a policy **AmazonEC2ContainerRegistryFullAccess**, suficiente para interagir com o ECR. Confira o CLI e configure a credencial:

```bash
aws --version
aws configure
```

No `aws configure` entram a access key, a secret key e a região (eu estava na Virgínia, `us-east-1`). O formato de saída pode ficar em branco.

> **Importante:** essas chaves autorizam qualquer pessoa a gerenciar o seu ECR. Não mostre para ninguém. Se elas vazarem, apague essas credenciais.

O `build.sh` nasce sem permissão de execução. Dê a permissão e rode o script:

```bash
chmod +x build.sh
./build.sh
```

![O ./build.sh rodando: Login Succeeded, os dois passos do build (FROM nginx e COPY meu_html), a tag meu-nginx:latest e as camadas em Pushed (conta coberta)](https://pages.formacaoaws.com.br/blog/assets/posts/primeiro-container-em-producao-no-ecs/build-sh-push-ecr.webp)

O push terminou rápido, e a imagem apareceu no repositório com 56 MB.

## Passo 3: criar o cluster com uma instância EC2

Crie um cluster com instâncias EC2:

- **Instância:** t3.micro. Daria para usar a t2.micro, mas a t3 é mais nova.
- **Key pair:** pode ser qualquer uma, porque não vamos nos conectar na máquina.
- **Security group:** crie um liberando a porta 80 (HTTP) para todo mundo, já que a aplicação é acessada por ela.
- **Rede:** a VPC padrão, qualquer subnet e IP público habilitado (na subnet padrão já vem assim).
- **IAM role da instância:** é o que dá permissão para o agente do ECS falar com a AWS. Crie uma nova ou use uma existente.

Em ECS Instances você vê a instância se registrar no cluster.

## Passo 4: criar a task definition

Crie uma task definition para EC2, sem task role, porque o container não precisa falar com nenhum serviço da AWS. Na definição do container:

- **Nome do container:** `meu-nginx`.
- **Imagem:** o endereço da imagem no ECR, o mesmo para onde o push mandou.
- **Memória:** existe o hard limit e o soft limit. Um é quanto você reserva, o outro é o teto antes de o container ser morto por pedir memória demais. Coloquei só um soft limit de 400 MB.
- **Porta:** local, eu tinha usado 8081. Aqui ficou a 80 dos dois lados, que é mais fácil de expor.

## Passo 5: criar o service que lança a task

O service é o responsável por lançar a task:

- **Cluster:** o que você acabou de criar. **Launch type:** EC2.
- **Service type:** réplica. Quem está começando não precisa se preocupar com o modo daemon.
- **Número de tasks:** 1.
- **Minimum healthy percent 0 e maximum percent 100:** esses valores dizem quanto do serviço fica disponível durante um deploy. Com 0, o ECS pode derrubar a task antiga antes de lançar a nova. Usei assim porque não há load balancer nem outros elementos envolvidos.
- **Task placement, load balancer e auto scaling:** não mexi.

A task passa de pendente para running. No detalhe dela aparece o link externo. Chame `hello.htm` nesse endereço e a aplicação está no ar.

![A task no ECS: meu-nginx em RUNNING, Container Port 80 e o link externo (imagem e IP cobertos)](https://pages.formacaoaws.com.br/blog/assets/posts/primeiro-container-em-producao-no-ecs/task-rodando.webp)

## Passo 6: derrubar de propósito

Esse modelo serve para produção porque o ECS gerencia a disponibilidade. Faça dois testes:

1. **Pare a task.** A aplicação cai, claro. Em pouco tempo o ECS percebe que não há nenhuma task rodando, quando o requisito era pelo menos uma, e lança outra.
2. **Termine a instância EC2.** O serviço cai de novo. Como há serviços por baixo do ECS cuidando disso, o Auto Scaling entre eles, ele sobe outra instância, ela entra no cluster e o service lança a task de novo. Isso leva uns 3 minutos, porque ele precisa lançar outra EC2. A aplicação volta com outro IP.

## Passo 7: publicar uma versão nova

Enquanto a instância nova subia, mudei o HTML para "v3" e rodei o `build.sh` de novo. Como a máquina era nova e baixou a imagem, já apareceu a v3.

Depois fiz a v4 com a instância já de pé. Subi a imagem, e a aplicação continuou na v3: ela só muda quando você faz um novo deployment. No service, use **update** e marque para **forçar um novo deployment**.

> **Importante:** forcei porque não trabalhei com tags diferentes. A imagem nova tem a mesma tag e código novo, e o force manda a instância baixar a versão atualizada. Num pipeline de CI/CD, que trabalha com tagueamento, cada versão gera uma task definition diferente e você não precisa forçar.

Com a regra 0 e 100, o ECS tira a task antiga do ar e lança a nova. Foi rápido, e a v4 apareceu.

![Minimum healthy percent 0 e maximum percent 100; o deployment novo (PRIMARY) ainda com 0 tasks rodando ao lado do antigo (ACTIVE)](https://pages.formacaoaws.com.br/blog/assets/posts/primeiro-container-em-producao-no-ecs/deploy-forcado.webp)

No fim, delete o cluster. A instância EC2 aparece como terminada.

## Na versão de 2024 da aula: erro de arquitetura e deploy pelo terminal

Esta parte vem da [versão de 2024 da aula](https://www.youtube.com/watch?v=x0bfNHo88Cg). O console do ECS mudou desde 2022, então as telas podem diferir do que você vê aqui; a [documentação oficial do ECS](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/Welcome.html) descreve o console atual.

**O erro `exec format error`.** Criei o service e a task passou de pendente para **Stopped**, com exit code 1. No log da task apareceu:

```
exec /docker-entrypoint.sh: exec format error
```

![Task em Stopped; o log mostra exec /docker-entrypoint.sh: exec format error](https://pages.formacaoaws.com.br/blog/assets/posts/primeiro-container-em-producao-no-ecs/exec-format-error.webp)

A causa: eu gerei a imagem num Mac M1, que é ARM, e a instância t3.micro é x86. Quem builda em x86 não tem problema. No ARM, troque o build por um que gere a imagem para a plataforma da instância (nessa aula a imagem se chama `aula-ecs-v2`):

```bash
docker buildx build --platform linux/amd64 -t aula-ecs-v2 .
```

Rode o `build.sh` de novo e force um novo deployment no service. A outra saída é lançar uma instância ARM, a t4g.micro, e não precisa de adaptação.

**Deploy pela linha de comando.** Em vez de clicar no console, criei um `deploy.sh` que roda o build e depois atualiza o service, com o nome do cluster e do service que a aula criou:

```bash
./build.sh
aws ecs update-service --cluster cluster-aula-ecs-v2 --service service-aula-ecs-v2 --force-new-deployment --profile aula-ecs-v2
```

O `--force-new-deployment` está aí pelo mesmo motivo de antes: a imagem nova tem a mesma tag.

**Perfil nomeado do CLI.** Trabalho com várias contas, então cada uma tem um profile. Para esta aula criei um usuário IAM com **AmazonEC2ContainerRegistryFullAccess** e **AmazonECSFullAccess**, gerei a access key e configurei o profile (região Virgínia e saída em tabela):

```bash
aws configure --profile aula-ecs-v2
aws ecr describe-repositories --profile aula-ecs-v2
```

Se o segundo comando lista o repositório, a credencial está certa e o `build.sh` já funciona com `--profile` no login do ECR.

> **Importante:** essas duas policies são amplas, e uma access key fica guardada na máquina até alguém apagá-la. Fora de um teste como este, dê só a permissão de que o fluxo precisa e prefira credenciais temporárias, como as do IAM Identity Center. Veja as [boas práticas de segurança do IAM](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html).

Na criação do service desativei o **circuit breaker**. Para quem está começando, é melhor ver o erro de cara do que deixar o rollback automático esconder a causa.

## Checklist

- Container rodando local e conferido no navegador.
- Repositório no Amazon ECR e os push commands num `build.sh`, com o `-f` do Dockerfile.
- AWS CLI com um usuário IAM só com a permissão do ECR, chaves bem guardadas.
- Cluster com EC2, security group liberando a porta 80 e IP público.
- Task definition apontando para a imagem do ECR, com limite de memória e a porta 80 mapeada.
- Service em modo réplica, com 1 task.
- Task parada e EC2 terminada, e o ECS repondo as duas.
- Versão nova com a mesma tag publicada forçando um novo deployment.
- Cluster deletado no fim do teste.

Isto é o básico: uma task, sem load balancer e com deploy manual. Depois que você vê o ECS repor uma máquina sozinho, gerenciar container na mão deixa de fazer sentido.
