---
title: "Docker Compose na VPS: aplicação e banco de dados em um compose.yaml"
description: "Suba uma aplicação com banco de dados usando Docker Compose na VPS: compose.yaml, volumes, arquivo .env, restart, atualização e o cuidado com portas e UFW."
url: "https://streethosting.com.br/guias/vps/docker-compose-vps-guia"
category: "vps"
slug: "docker-compose-vps-guia"
datePublished: "2026-11-13"
dateModified: "2026-11-13"
author: "Equipe StreetHosting"
difficulty: "intermediario"
language: "pt-BR"
keywords:
  - "docker compose vps"
  - "compose.yaml exemplo"
  - "docker compose up -d"
  - "docker volume ou bind mount"
  - "docker ufw porta exposta"
  - "atualizar container docker compose"
---

# Docker Compose na VPS: um compose.yaml com aplicação, banco e dados que sobrevivem

Um arquivo descreve a aplicação, o banco e os volumes, e um comando sobe tudo. Veja o compose.yaml completo, os comandos do dia a dia, como atualizar sem perder dados e as duas armadilhas que pegam todo mundo.

> **Resposta rápida**
>
> O **Docker Compose** descreve a aplicação, o banco e os volumes em um único arquivo, o `compose.yaml`, e sobe tudo com `docker compose up -d`. Use um volume nomeado para os dados do banco, `restart: unless-stopped` para voltar sozinho após um reboot e um arquivo `.env` para as senhas. Dois avisos: porta publicada pelo Docker passa por fora do UFW, então publique em 127.0.0.1 o que não deve ser público, e `docker compose down -v` apaga os volumes.

## Por que usar o Compose

Rodar um container com `docker run` funciona até a hora de repetir o comando. Uma aplicação com banco já pede dois containers, uma rede entre eles, um volume, variáveis de ambiente e uma política de reinício. Guardar isso em uma linha de terminal é receita para esquecer um parâmetro no próximo deploy.

O Compose resolve isso com um arquivo que fica junto do projeto, pode ir para o Git e documenta exatamente como o ambiente sobe. O mesmo arquivo vale no seu computador e na VPS. Este guia parte de uma VPS com o Docker já instalado, como mostra o guia de [instalar Docker no Ubuntu](https://streethosting.com.br/guias/vps/instalar-docker-ubuntu-vps). Confirme com `docker compose version`.

## O compose.yaml

O exemplo abaixo sobe uma aplicação web que escuta na porta 3000 e um PostgreSQL. Crie uma pasta para o projeto, por exemplo `~/meuapp`, e salve o arquivo nela como `compose.yaml`.

```
services:
  app:
    build: .
    restart: unless-stopped
    env_file: .env
    ports:
      - "127.0.0.1:3000:3000"
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:16
    restart: unless-stopped
    environment:
      POSTGRES_USER: ${DB_USER}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: ${DB_NAME}
    volumes:
      - dbdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"]
      interval: 5s
      timeout: 3s
      retries: 10

volumes:
  dbdata:
```

O que cada parte faz:

- **build: .** constrói a imagem da aplicação a partir do `Dockerfile` da pasta. Se você usa uma imagem pronta, troque por `image: nome/imagem:tag`.
- **restart** com o valor `unless-stopped` reinicia o container se ele cair e o sobe de novo quando a VPS reinicia, a menos que você o tenha parado de propósito.
- **ports** publica a porta da aplicação. Repare no `127.0.0.1:` na frente: ela só fica acessível de dentro da própria VPS, onde o Nginx vai buscar a aplicação. A seção sobre armadilhas explica o motivo.
- **O banco não publica porta nenhuma.** Os dois serviços ficam na mesma rede criada pelo Compose, e a aplicação encontra o banco pelo nome do serviço, `db`, na porta 5432. De fora da VPS ninguém alcança o banco.
- **depends_on com healthcheck** faz a aplicação esperar o PostgreSQL aceitar conexões, em vez de subir junto e falhar na primeira tentativa.
- **volumes** declara o volume `dbdata`, onde o PostgreSQL guarda os dados. Sem ele, os dados morreriam junto com o container.

Não escreva a linha `version:` no topo. Ela está obsoleta no Compose atual. Para conferir se o arquivo está válido e ver como as variáveis foram substituídas, rode `docker compose config`.

## Segredos no arquivo .env

O Compose lê automaticamente um arquivo chamado `.env` na mesma pasta e usa os valores nas expressões `${NOME}` do compose.yaml. A linha `env_file: .env` do serviço app passa essas mesmas variáveis para dentro do container. Assim as senhas ficam em um arquivo só, fora do compose.yaml.

```
DB_USER=meuapp
DB_PASSWORD=troque_por_uma_senha_longa
DB_NAME=meuapp
DATABASE_URL=postgres://meuapp:troque_por_uma_senha_longa@db:5432/meuapp
```

Três cuidados com esse arquivo:

1. Nunca o envie para o Git. Coloque `.env` no `.gitignore` e, se quiser, versione um `.env.example` sem os valores reais.
2. Restrinja a leitura na VPS com `chmod 600 .env`.
3. Gere uma senha forte, por exemplo com `openssl rand -hex 24`. Hexadecimal evita caracteres como @, : e / que quebrariam a DATABASE_URL.

> **Atenção**
>
> O PostgreSQL só lê POSTGRES_USER, POSTGRES_PASSWORD e POSTGRES_DB na primeira vez em que o volume está vazio. Se você mudar a senha no `.env` depois, o banco continua com a antiga, porque os dados já existem no volume. A troca precisa ser feita dentro do banco, com ALTER USER.

## Os comandos do dia a dia

Todos os comandos abaixo rodam dentro da pasta do projeto, onde está o compose.yaml.

| Comando | O que faz |
| --- | --- |
| docker compose up -d | Cria e inicia tudo em segundo plano. Se já existe, só aplica o que mudou. |
| docker compose ps | Lista os serviços e mostra se estão rodando e saudáveis. |
| docker compose logs -f app | Acompanha o log do serviço app em tempo real. Ctrl+C sai sem parar o container. |
| docker compose logs --tail 100 db | Mostra as últimas 100 linhas do log do banco. |
| docker compose restart app | Reinicia só o serviço app. |
| docker compose exec db psql -U meuapp | Abre um terminal do PostgreSQL dentro do container do banco. |
| docker compose stop | Para os containers sem apagar nada. |
| docker compose down | Para e remove containers e rede. Os volumes ficam. |

Na primeira execução, rode o `up -d` e depois o `logs -f` para ver a aplicação conectar no banco. Se ela cair em loop, o log mostra o erro e o `ps` mostra o status como restarting.

> **Dica**
>
> O Docker guarda o log de cada container em arquivo, e por padrão esse arquivo cresce sem limite. Em uma aplicação que escreve muito log, ele pode encher o disco. Dentro de cada serviço, a opção `logging` com o driver `json-file` e os limites `max-size` e `max-file` resolve. Se o disco já apertou, o guia de [descobrir o que ocupa espaço na VPS](https://streethosting.com.br/guias/vps/descobrir-o-que-ocupa-espaco-vps) ajuda a achar o culpado.

## Volume nomeado ou bind mount

Existem duas formas de guardar dados fora do container, e a escolha muda o que você enxerga e o que pode dar errado.

|  | Volume nomeado | Bind mount |
| --- | --- | --- |
| Como declarar | dbdata:/var/lib/postgresql/data | ./dados:/var/lib/postgresql/data |
| Onde fica no disco | Gerenciado pelo Docker, em /var/lib/docker/volumes | Na pasta que você escolheu |
| Permissões | O Docker acerta sozinho | Você precisa casar o usuário do container com o dono da pasta |
| Editar do host | Incômodo | Direto, com qualquer editor |
| Indicado para | Dados de banco e de aplicação | Arquivos de configuração e código em desenvolvimento |

A regra prática: banco de dados vai em volume nomeado. Arquivo que você quer abrir e editar, como um `nginx.conf` ou o diretório de uploads de um site, vai em bind mount. Para um bind mount de arquivo de configuração que o container só lê, acrescente `:ro` ao final, como em `./nginx.conf:/etc/nginx/nginx.conf:ro`.

Para ver os volumes existentes use `docker volume ls`. O Compose prefixa o nome com o da pasta do projeto, então o volume do exemplo aparece como algo parecido com `meuapp_dbdata`. Volume não é backup: ele protege contra recriar o container, não contra disco com problema ou comando errado. Faça cópias do banco com `docker compose exec db pg_dump` e guarde fora da VPS.

## Atualizar a aplicação

O ciclo de atualização depende de onde vem a imagem.

**Imagem pronta de um registro** (como a do PostgreSQL ou uma que você publicou):

```
docker compose pull
docker compose up -d
docker image prune -f
```

O `pull` baixa as versões novas. O `up -d` recria apenas os serviços cuja imagem mudou, mantendo os volumes. O `image prune` apaga imagens antigas que ficaram sem uso.

**Aplicação construída do seu código** (o `build: .` do exemplo): atualize o código com `git pull` e rode `docker compose up -d --build`.

Duas dicas para não se arrepender. Fixe a versão do banco no compose.yaml, como `postgres:16`, em vez de `latest`: saltar de uma versão principal para outra sem migrar os dados faz o PostgreSQL se recusar a iniciar. E antes de atualizar o banco, copie os dados com pg_dump. Para automatizar o deploy em cada push, veja [deploy com GitHub Actions na VPS](https://streethosting.com.br/guias/vps/deploy-github-actions-vps).

## Duas armadilhas: portas publicadas e volumes apagados

**1. A porta publicada passa por fora do UFW.** Muita gente ativa o UFW, bloqueia tudo, roda `ports: - "5432:5432"` no Compose e descobre dias depois que o banco está aberto para a internet. O motivo é que o Docker insere as próprias regras no iptables, e o tráfego para uma porta publicada é desviado para o container antes de chegar às regras do UFW. O `ufw deny` não tem efeito sobre ela.

A defesa é não publicar o que não precisa ser público:

- **Banco, cache e filas:** não declare `ports`. Os outros serviços do mesmo compose os alcançam pelo nome, na rede interna.
- **Aplicação que fica atrás do Nginx:** publique só em 127.0.0.1, como `127.0.0.1:3000:3000`, e deixe o Nginx fazer o proxy, como no guia de [Nginx como proxy reverso](https://streethosting.com.br/guias/vps/configurar-nginx-reverse-proxy-vps).
- **Quando você quer a porta pública mesmo:** publique, mas saiba que está pública. Para filtrar quem acessa, as regras do UFW não bastam e é preciso usar a cadeia DOCKER-USER do iptables.

Depois de subir, confira de fora da VPS. De outro computador, teste a porta com um verificador de portas ou com `nc -zv IP_DA_VPS 5432`. Na VPS, o comando `sudo ss -tlnp` mostra em qual endereço cada porta está escutando: 0.0.0.0 é público, 127.0.0.1 é local. O guia de [firewall UFW na VPS](https://streethosting.com.br/guias/vps/firewall-ufw-vps-ubuntu) cobre o resto das regras.

**2. O down com a opção `-v` apaga os volumes.** O `docker compose down` é seguro para os dados. Já o `docker compose down -v` remove também os volumes nomeados do projeto, ou seja, o banco inteiro, sem pedir confirmação e sem como desfazer. O mesmo risco aparece com `docker volume prune -a` depois de um down: sem nenhum container usando o volume, ele entra na limpeza.

> **Atenção**
>
> Use o `-v` só quando quiser mesmo recomeçar do zero, como em um ambiente de teste. Em produção, tire um pg_dump antes de qualquer comando que tenha down, prune ou rm no meio.

## Onde rodar isso

O Docker precisa de um kernel Linux completo e de acesso root para criar redes, volumes e regras de firewall. Nas [VPS da StreetHosting](https://streethosting.com.br/vps) a virtualização é KVM, com kernel próprio e acesso root, então o Docker e o Compose rodam do jeito que estão neste guia.

- **Aplicação pequena com banco:** a VPS Xeon a partir de R$ 26,00 por mês, com 2 vCPU, 2 GB DDR4 e 20 GB NVMe, comporta um stack como o deste guia. Acompanhe a memória com o Compose rodando, porque cada serviço soma ao total.
- **Vários serviços ou carga maior:** a [VPS Ryzen 9 9950X](https://streethosting.com.br/vps/ryzen) começa em R$ 43,00 por mês com 1 vCPU e 2 GB DDR5 e cresce até 64 GB, sempre com NVMe.

Todas ficam em São Paulo, com AntiDDoS incluso, até 5 TB de tráfego por mês e ativação em cerca de 60 segundos depois do Pix ou do cartão aprovado.

## Perguntas frequentes

### Qual a diferença entre docker compose e a versão antiga com hífen?

A versão antiga, com hífen entre as palavras, foi escrita em Python e está descontinuada. O docker compose, com espaço, é o plugin atual do Docker e é o que o guia de instalação traz. Os comandos e o arquivo são praticamente os mesmos, e hoje o nome de arquivo recomendado é compose.yaml.

### Preciso escrever version no topo do compose.yaml?

Não. O campo version ficou obsoleto e o Compose atual só mostra um aviso se ele estiver no arquivo. Comece direto por services.

### O banco de dados some quando eu reinicio os containers?

Não, desde que os dados estejam em um volume. Parar, reiniciar ou recriar o container preserva o volume. Os dados só vão embora se você apagar o volume, por exemplo com docker compose down -v.

### Por que o UFW não bloqueia a porta que o Docker publicou?

O Docker escreve as próprias regras de firewall e o tráfego para portas publicadas passa antes das regras do UFW. Para a porta não ficar pública, publique só no endereço local, como 127.0.0.1:3000:3000, ou não publique a porta e deixe os containers conversando pela rede interna.

### Como atualizar a imagem de um serviço sem perder os dados?

Rode docker compose pull para baixar a imagem nova e depois docker compose up -d. O Compose recria só os serviços cuja imagem mudou, e os volumes continuam intactos. Se a aplicação é construída do seu código, use docker compose up -d --build.

## Guias relacionados

- [Como instalar Docker no Ubuntu 22.04 ou 24.04 em VPS (guia direto ao ponto)](https://streethosting.com.br/guias/vps/instalar-docker-ubuntu-vps.md)
- [Como configurar o Nginx como reverse proxy na VPS](https://streethosting.com.br/guias/vps/configurar-nginx-reverse-proxy-vps.md)
- [UFW na VPS Ubuntu: regras de firewall sem perder o SSH](https://streethosting.com.br/guias/vps/firewall-ufw-vps-ubuntu.md)
- [Como instalar WordPress com Docker Compose na VPS](https://streethosting.com.br/guias/vps/instalar-wordpress-docker-vps.md)
- [Como instalar o Portainer para gerenciar Docker na VPS](https://streethosting.com.br/guias/vps/instalar-portainer-docker-vps.md)

## Produtos citados

- https://streethosting.com.br/vps
- https://streethosting.com.br/vps/ryzen

## Dados estruturados

```json
[
  {
    "@context": "https://schema.org",
    "@type": [
      "Article",
      "TechArticle"
    ],
    "headline": "Docker Compose na VPS: um compose.yaml com aplicação, banco e dados que sobrevivem",
    "name": "Docker Compose na VPS: aplicação e banco de dados em um compose.yaml",
    "abstract": "Um arquivo descreve a aplicação, o banco e os volumes, e um comando sobe tudo. Veja o compose.yaml completo, os comandos do dia a dia, como atualizar sem perder dados e as duas armadilhas que pegam todo mundo.",
    "description": "Suba uma aplicação com banco de dados usando Docker Compose na VPS: compose.yaml, volumes, arquivo .env, restart, atualização e o cuidado com portas e UFW.",
    "datePublished": "2026-11-13",
    "dateModified": "2026-11-13",
    "author": {
      "@type": "Organization",
      "name": "Equipe StreetHosting"
    },
    "publisher": {
      "@type": "Organization",
      "name": "StreetHosting",
      "url": "https://streethosting.com.br"
    },
    "inLanguage": "pt-BR",
    "mainEntityOfPage": {
      "@type": "WebPage",
      "@id": "https://streethosting.com.br/guias/vps/docker-compose-vps-guia"
    }
  },
  {
    "@context": "https://schema.org",
    "@type": "FAQPage",
    "mainEntity": [
      {
        "@type": "Question",
        "name": "Qual a diferença entre docker compose e a versão antiga com hífen?",
        "acceptedAnswer": {
          "@type": "Answer",
          "text": "A versão antiga, com hífen entre as palavras, foi escrita em Python e está descontinuada. O docker compose, com espaço, é o plugin atual do Docker e é o que o guia de instalação traz. Os comandos e o arquivo são praticamente os mesmos, e hoje o nome de arquivo recomendado é compose.yaml."
        }
      },
      {
        "@type": "Question",
        "name": "Preciso escrever version no topo do compose.yaml?",
        "acceptedAnswer": {
          "@type": "Answer",
          "text": "Não. O campo version ficou obsoleto e o Compose atual só mostra um aviso se ele estiver no arquivo. Comece direto por services."
        }
      },
      {
        "@type": "Question",
        "name": "O banco de dados some quando eu reinicio os containers?",
        "acceptedAnswer": {
          "@type": "Answer",
          "text": "Não, desde que os dados estejam em um volume. Parar, reiniciar ou recriar o container preserva o volume. Os dados só vão embora se você apagar o volume, por exemplo com docker compose down -v."
        }
      },
      {
        "@type": "Question",
        "name": "Por que o UFW não bloqueia a porta que o Docker publicou?",
        "acceptedAnswer": {
          "@type": "Answer",
          "text": "O Docker escreve as próprias regras de firewall e o tráfego para portas publicadas passa antes das regras do UFW. Para a porta não ficar pública, publique só no endereço local, como 127.0.0.1:3000:3000, ou não publique a porta e deixe os containers conversando pela rede interna."
        }
      },
      {
        "@type": "Question",
        "name": "Como atualizar a imagem de um serviço sem perder os dados?",
        "acceptedAnswer": {
          "@type": "Answer",
          "text": "Rode docker compose pull para baixar a imagem nova e depois docker compose up -d. O Compose recria só os serviços cuja imagem mudou, e os volumes continuam intactos. Se a aplicação é construída do seu código, use docker compose up -d --build."
        }
      }
    ]
  }
]
```
