Se você quer aprender Docker, ficar apenas estudando o que é imagem, container e volume não ajuda muito.
A melhor forma de entender é colocando uma aplicação para funcionar.
Neste tutorial de Docker para iniciantes, vamos criar uma API Node.js conectada ao PostgreSQL usando Docker Compose.
No final teremos:
- Node.js em um container
- PostgreSQL em outro
- rede entre os serviços
- volume para persistir o banco
- variáveis de ambiente
- tudo iniciado com um único comando
O que vamos criar
Nossa aplicação terá duas rotas:
POST /users
GET /users
A estrutura será:
docker-node-postgres/
├── src/
│ └── server.js
├── .env
├── .dockerignore
├── compose.yaml
├── Dockerfile
└── package.json
1. Criando o projeto Node.js
Comece criando o projeto:
mkdir docker-node-postgres
cd docker-node-postgres
npm init -y
npm install express pg
Crie o arquivo:
src/server.js
E adicione:
const express = require("express");
const { Pool } = require("pg");
const app = express();
app.use(express.json());
const pool = new Pool({
host: process.env.DB_HOST,
port: process.env.DB_PORT,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
});
async function initializeDatabase() {
await pool.query(`
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(150) UNIQUE NOT NULL
)
`);
}
app.get("/users", async (request, response) => {
const result = await pool.query(
"SELECT * FROM users ORDER BY id DESC"
);
return response.json(result.rows);
});
app.post("/users", async (request, response) => {
const { name, email } = request.body;
const result = await pool.query(
`
INSERT INTO users (name, email)
VALUES ($1, $2)
RETURNING *
`,
[name, email]
); return response.status(201).json(result.rows[0]); }); async function start() { await initializeDatabase(); app.listen(3000, “0.0.0.0”, () => { console.log(“Servidor rodando na porta 3000”); }); } start();
Até aqui temos uma API Node.js comum.
A diferença é que o PostgreSQL não estará instalado diretamente na nossa máquina. Ele ficará em outro container.
2. Criando o Dockerfile
Na raiz do projeto, crie:
Dockerfile
Adicione:
FROM node:24-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
EXPOSE 3000
CMD ["node", "src/server.js"]
Esse arquivo define como a imagem da nossa aplicação será construída.
O fluxo é basicamente:
Dockerfile
↓
Imagem da aplicação
↓
Container Node.js
3. Criando o .dockerignore
Crie:
.dockerignore
Adicione:
node_modules
.env
.git
Assim evitamos copiar arquivos desnecessários para dentro da imagem Docker.
4. Configurando as variáveis de ambiente
Crie o arquivo:
.env
Adicione:
DB_HOST=db
DB_PORT=5432
DB_USER=postgres
DB_PASSWORD=postgres
DB_NAME=app
O ponto mais importante aqui é:
DB_HOST=db
E não:
DB_HOST=localhost
Isso acontece porque Node.js e PostgreSQL estarão em containers diferentes.
Dentro do container da API, localhost aponta para o próprio container da API.
db será o nome do serviço PostgreSQL dentro do Docker Compose.
5. Criando o Docker Compose
Agora crie:
compose.yaml
Adicione:
services:
api:
build: .
ports:
- "3000:3000"
environment:
DB_HOST: db
DB_PORT: 5432
DB_USER: ${DB_USER}
DB_PASSWORD: ${DB_PASSWORD}
DB_NAME: ${DB_NAME}
depends_on:
db:
condition: service_healthy
networks:
- app_network
db:
image: postgres:18
environment:
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: ${DB_NAME}
volumes:
- postgres_data:/var/lib/postgresql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"]
interval: 5s
timeout: 5s
retries: 5
networks:
- app_network
volumes:
postgres_data:
networks:
app_network:
Agora temos praticamente toda a infraestrutura do projeto descrita em um único arquivo.
Entendendo o Docker Compose
Temos dois serviços:
api
Nossa aplicação Node.js.
db
Nosso PostgreSQL.
A estrutura fica assim:
Node.js
|
| app_network
|
PostgreSQL
|
postgres_data
Rede entre os containers
Os dois serviços utilizam a mesma rede:
networks:
- app_network
Isso permite que eles se comuniquem.
Por isso nossa API consegue acessar:
db:5432
O Docker resolve automaticamente db para o serviço PostgreSQL.
Persistindo o PostgreSQL com volume
No serviço do banco temos:
volumes:
- postgres_data:/var/lib/postgresql
O container pode ser removido, mas os dados continuam armazenados no volume.
Isso é importante porque containers são descartáveis.
O banco, normalmente, não deve ser.
Por que usar healthcheck?
Também adicionamos:
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"]
E na API:
depends_on:
db:
condition: service_healthy
Sem isso, poderia acontecer:
Node.js inicia
↓
tenta conectar no banco
↓
PostgreSQL ainda está inicializando
↓
erro de conexão
Com o healthcheck, a API espera o banco estar realmente disponível.
6. Subindo o projeto
Agora execute:
docker compose up --build
O Docker irá:
- construir a imagem da API
- baixar a imagem do PostgreSQL
- criar a rede
- criar o volume
- iniciar o PostgreSQL
- iniciar a API
A aplicação estará disponível em:
http://localhost:3000
7. Testando a API
Crie um usuário:
POST http://localhost:3000/users
Body:
{
"name": "Matheus",
"email": "matheus@email.com"
}
Depois consulte:
GET http://localhost:3000/users
A resposta será semelhante a:
[
{
"id": 1,
"name": "Matheus",
"email": "matheus@email.com"
}
]
Agora temos o fluxo completo:
Request
↓
Node.js
↓
Rede Docker
↓
PostgreSQL
↓
Volume
8. Testando a persistência
Pare os containers:
docker compose down
Depois suba novamente:
docker compose up
Consulte novamente:
GET /users
Os dados continuam lá.
Isso acontece porque o PostgreSQL está utilizando o volume:
postgres_data
Agora, se você executar:
docker compose down -v
o Docker também remove o volume.
Nesse caso, os dados do banco serão apagados.
Comandos Docker que vale conhecer
Subir o projeto:
docker compose up
Reconstruir a imagem:
docker compose up --build
Executar em segundo plano:
docker compose up -d
Ver os containers:
docker compose ps
Ver os logs:
docker compose logs -f
Parar:
docker compose down
Parar e remover volumes:
docker compose down -v
Dockerfile x Docker Compose
Essa diferença costuma confundir bastante quem está começando.
Dockerfile
Define como criar a imagem da aplicação.
Código Node.js
↓
Dockerfile
↓
Imagem
Docker Compose
Define como os diferentes serviços da aplicação funcionam juntos.
Node.js
+
PostgreSQL
+
Rede
+
Volume
Por isso utilizamos os dois.
Conclusão
Neste tutorial de Docker para iniciantes, criamos uma aplicação completa usando:
- Node.js
- PostgreSQL
- Dockerfile
- Docker Compose
- variáveis de ambiente
- rede entre containers
- volume persistente
- healthcheck
Mais importante do que decorar comandos é entender o papel de cada parte.
O Dockerfile cria a imagem da sua aplicação.
O Docker Compose conecta os serviços que ela precisa.
A rede permite que os containers conversem entre si.
E o volume mantém os dados mesmo quando os containers são removidos.
A partir daqui, você já tem uma base suficiente para começar a colocar suas próprias APIs dentro do Docker.
