Jhonatan Pinheiro
Cargando página...
Jhonatan Pinheiro
Cargando página...
Jhonatan Pinheiro
Cargando página...
Contenedores, imágenes, Dockerfile multi-stage, volúmenes, redes, Compose y qué cambia en producción: 150 comandos con ejemplos listos.
Docker resuelve un problema viejo: "en mi máquina funciona". Empaqueta la aplicación con todo lo que necesita — runtime, bibliotecas, configuración — en una imagen que se ejecuta igual en cualquier sitio.
Estos 150 comandos van desde el primer docker run hasta el diagnóstico de un contenedor que muere en producción. Los ejemplos usan la CLI moderna (docker container ...) y mencionan el atajo clásico cuando es el más usado.
Un contenedor no es una máquina virtual. Comparte el kernel del host y aísla solo lo que necesita (procesos, red, sistema de archivos). Por eso arranca en milisegundos y pesa megabytes en lugar de gigabytes.
Verificar la instalación, levantar el primer contenedor y entender qué pasó.
Confirma que el cliente habla con el daemon.
docker version
docker infoDescarga la imagen, crea el contenedor, lo ejecuta y sale. El "hola mundo" de Docker.
docker run hello-worldCrea y arranca un contenedor a partir de una imagen.
docker run nginx-d (detached) te devuelve el terminal y deja el contenedor funcionando.
docker run -d nginx-p host:contenedor expone el servicio en tu máquina.
docker run -d -p 8080:80 nginx
# entra en http://localhost:8080Sin --name, Docker inventa un nombre aleatorio y pierdes tiempo buscándolo.
docker run -d --name meu-nginx -p 8080:80 nginx-it conecta tu terminal al contenedor — imprescindible para una shell.
docker run -it ubuntu bash--rm borra el contenedor en cuanto se para. Ideal para una prueba rápida.
docker run --rm -it alpine shLa forma estándar de configurar una aplicación en contenedor.
docker run -d -e POSTGRES_PASSWORD=senha -e POSTGRES_DB=loja postgres:16Evita exponer el secreto en el historial de la shell.
docker run --env-file .env -d minha-appEl comando que más vas a escribir.
docker ps-a incluye los parados — donde suele estar lo que falló.
docker ps -aDeja la lista legible en un terminal estrecho.
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"stop envía SIGTERM y espera; start vuelve a encender el mismo contenedor.
docker stop meu-nginx
docker start meu-nginxAtajo para stop + start.
docker restart meu-nginxSIGKILL inmediato, sin cierre elegante. Último recurso.
docker kill meu-nginxBorra el contenedor parado; -f lo fuerza incluso en ejecución.
docker rm meu-nginx
docker rm -f meu-nginxLo primero que hay que hacer cuando algo no funciona.
docker logs meu-nginx-f sigue la salida; --tail limita el historial inicial.
docker logs -f --tail 100 meu-nginxAbre una shell dentro del contenedor en ejecución.
docker exec -it meu-nginx bash
docker exec -it meu-alpine sh # las imágenes Alpine no tienen bashLa imagen es el molde; el contenedor es la instancia en ejecución.
Muestra lo que está descargado y cuánto ocupa.
docker images
docker image lsLa trae del registro sin ejecutarla.
docker pull postgres:16Nunca uses latest en producción: cambia sin avisar y rompe lo que funcionaba.
docker pull node:20-alpine
docker pull node:20.11.1-alpine3.19 # todavía más específicoBusca en Docker Hub desde la línea de comandos.
docker search postgresLee el Dockerfile del directorio y genera la imagen con la etiqueta indicada.
docker build -t minha-app:1.0 .Cuando hay más de uno (producción, desarrollo, pruebas).
docker build -f Dockerfile.prod -t minha-app:prod .Fuerza a rehacer todas las capas — útil cuando la caché está tapando un problema.
docker build --no-cache -t minha-app:1.0 .ARG parametriza el build (no confundir con ENV, que vale en ejecución).
docker build --build-arg NODE_VERSION=20 -t minha-app .La misma imagen puede tener varios nombres.
docker tag minha-app:1.0 usuario/minha-app:latestPublica en Docker Hub o en un registro privado.
docker login
docker push usuario/minha-app:1.0El nombre de la imagen lleva la dirección del registro.
docker tag minha-app registry.empresa.com/time/minha-app:1.0
docker push registry.empresa.com/time/minha-app:1.0Solo se va si ningún contenedor depende de ella.
docker rmi minha-app:1.0
docker image rm -f minha-app:1.0Muestra cada capa y su tamaño — así se descubre qué engordó la imagen.
docker history minha-app:1.0Metadatos completos: variables, entrypoint, puertos, capas.
docker inspect minha-app:1.0Mueve una imagen entre máquinas sin registro, como archivo.
docker save -o minha-app.tar minha-app:1.0
docker load -i minha-app.tarCapas huérfanas de builds antiguos, que solo ocupan disco.
docker images -f "dangling=true"
docker image prune-a elimina todas las que no tienen contenedor asociado. Libera mucho espacio.
docker image prune -aAlpine pesa unos 5 MB; distroless ni siquiera tiene shell. Menos superficie, menos vulnerabilidades.
# 1.1 GB
FROM node:20
# 130 MB
FROM node:20-alpine
# ~50 MB, sin shell ni gestor de paquetes
FROM gcr.io/distroless/nodejs20-debian12Comprueba las CVE conocidas en las dependencias de la imagen.
docker scout cves minha-app:1.0
trivy image minha-app:1.0Compara el tamaño de las imágenes para encontrar la gorda.
docker images --format "{{.Repository}}:{{.Tag}}\t{{.Size}}" | sort -k2 -hCrea un contenedor temporal solo para copiar algo de dentro.
id=$(docker create minha-app:1.0)
docker cp $id:/app/dist ./dist
docker rm $idGenera la misma imagen para amd64 y arm64 — necesario para Apple Silicon y Graviton.
docker buildx build --platform linux/amd64,linux/arm64 -t usuario/app:1.0 --push .Inspeccionar, copiar, limitar y entender qué está pasando ahí dentro.
JSON completo: red, volúmenes, variables, estado, código de salida.
docker inspect meu-nginxCon una plantilla Go, coges solo lo que necesitas.
docker inspect -f '{{.State.Status}}' meu-nginx
docker inspect -f '{{.NetworkSettings.IPAddress}}' meu-nginxCPU, memoria, red y E/S de cada contenedor — el top de Docker.
docker stats
docker stats --no-streamLo que está corriendo ahí dentro, visto desde el host.
docker top meu-nginxMueve archivos entre el host y el contenedor en los dos sentidos.
docker cp ./config.json meu-nginx:/etc/app/config.json
docker cp meu-nginx:/var/log/nginx/error.log ./error.logLista los archivos creados (A), modificados (C) y eliminados (D) desde la imagen.
docker diff meu-nginxSin límite, un contenedor puede tumbar el host entero.
docker run -d --memory 512m --memory-swap 512m minha-app--cpus define cuántos núcleos puede usar el contenedor.
docker run -d --cpus 1.5 minha-appHace que el contenedor vuelva solo tras un fallo o un reinicio.
docker run -d --restart unless-stopped minha-app
# opciones: no | on-failure | on-failure:3 | always | unless-stoppedDocker pasa a saber si el contenedor está sano, no solo levantado.
docker run -d --health-cmd "curl -f http://localhost/health || exit 1" \
--health-interval 30s --health-retries 3 minha-appMuestra el resultado de las últimas comprobaciones.
docker inspect -f '{{.State.Health.Status}}' minha-appDice por qué murió el contenedor — 137 es OOM/kill, 1 es un error de la aplicación.
docker inspect -f '{{.State.ExitCode}}' minha-app
docker ps -a --filter "exited=137"Entra en el contenedor con un usuario concreto (útil para depurar permisos).
docker exec -u root -it minha-app shUn contenedor corriendo como root es un riesgo innecesario.
docker run -d --user 1000:1000 minha-appSin recrear nada.
docker rename nome-antigo nome-novoCongela los procesos sin terminar el contenedor.
docker pause minha-app
docker unpause minha-appBloquea hasta la salida y devuelve el código — útil en un script.
docker wait minha-appFlujo en vivo de todo lo que pasa: creación, muerte, salud, red.
docker events --filter container=minha-appDescubre a dónde está mapeado el contenedor.
docker port meu-nginxLimpieza rutinaria.
docker container pruneEmergencia o fin de jornada.
docker stop $(docker ps -q)Congela el estado actual como imagen. Sirve para depurar — nunca como forma de construir una imagen.
docker commit minha-app minha-app:debugLa receta de la imagen. Cada instrucción se convierte en una capa — y el orden importa mucho.
La imagen base. Primera instrucción de todo Dockerfile.
FROM node:20-alpineDefine el directorio de trabajo y lo crea si no existe. Mejor que RUN cd.
WORKDIR /appCopia del contexto de build al interior de la imagen.
COPY package.json package-lock.json ./
COPY src ./srcComo COPY, pero además descarga una URL y descomprime un tar. Prefiere COPY: menos sorpresas.
ADD https://exemplo.com/arquivo.tar.gz /tmp/Ejecuta un comando en el build y congela el resultado en una capa.
RUN npm ci --omit=devCada RUN es una capa. Junta los comandos relacionados y limpia en la misma línea.
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*El truco que más acelera el build: copia el manifiesto e instala antes de copiar el código.
COPY package*.json ./
RUN npm ci # solo se rehace cuando cambian las dependencias
COPY . . # cambia en cada commit, pero es la última capaVariable de entorno disponible en el build y en la ejecución.
ENV NODE_ENV=production PORT=3000Variable solo del build. No queda en la imagen final — pero sí aparece en el historial, así que nada de secretos.
ARG NODE_VERSION=20
FROM node:${NODE_VERSION}-alpineDocumenta el puerto usado. No publica nada por sí solo — quien publica es el -p.
EXPOSE 3000Comando por defecto del contenedor, sustituible en la línea de comandos.
CMD ["node", "server.js"]Comando fijo. Combinado con CMD, se convierte en "programa + argumentos por defecto".
ENTRYPOINT ["node"]
CMD ["server.js"]Usa siempre la forma exec (JSON): en la forma shell el proceso pasa a ser hijo de /bin/sh y no recibe las señales.
CMD ["node", "server.js"] # exec — recibe SIGTERM
CMD node server.js # shell — no la recibeCambia el usuario. Todo lo que venga después corre sin root.
RUN addgroup -S app && adduser -S app -G app
USER appMarca un directorio como punto de montaje de datos persistentes.
VOLUME ["/var/lib/postgresql/data"]Enseña a Docker a comprobar si la aplicación está respondiendo de verdad.
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD wget -qO- http://localhost:3000/health || exit 1Metadatos de la imagen: versión, autor, origen del código.
LABEL org.opencontainers.image.source="https://github.com/usuario/projeto"Reduce el contexto de build. Sin él, mandas node_modules y .git al daemon en cada build.
node_modules
.git
.env
dist
*.logCompila en una etapa y copia solo el resultado a la imagen final. Recorta cientos de MB.
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
USER node
CMD ["node", "dist/server.js"]--from acepta cualquier imagen, no solo una etapa anterior.
COPY --from=nginx:alpine /etc/nginx/nginx.conf /etc/nginx/nginx.confMantiene la caché del gestor entre builds, sin hinchar la imagen.
RUN --mount=type=cache,target=/root/.npm npm ciUsa la credencial durante el build sin grabarla en ninguna capa.
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc npm ci
# docker build --secret id=npmrc,src=$HOME/.npmrc .El contenedor es desechable; los datos no pueden serlo. Para eso existen los volúmenes.
Volumen con nombre, gestionado por Docker.
docker volume create dados-postgresMuestra dónde vive el volumen en el host.
docker volume ls
docker volume inspect dados-postgresLos datos sobreviven a la eliminación del contenedor.
docker run -d -v dados-postgres:/var/lib/postgresql/data postgres:16Monta una carpeta del host. Es lo que da hot reload en desarrollo.
docker run -d -v $(pwd)/src:/app/src minha-appMás explícita que -v y recomendada en los scripts.
docker run -d --mount type=bind,source=$(pwd)/src,target=/app/src minha-appUn contenedor que no debería escribir, no escribe.
docker run -d -v $(pwd)/config:/app/config:ro minha-appVolumen para datos de producción (lo gestiona Docker); bind mount para el código en desarrollo.
# producción
docker run -v dados:/var/lib/mysql mysql:8
# desarrollo
docker run -v $(pwd):/app node:20 npm run devSistema de archivos en memoria: desaparece cuando el contenedor se para. Bueno para datos sensibles.
docker run -d --tmpfs /tmp:rw,size=64m minha-appUn contenedor temporal que empaqueta el contenido del volumen.
docker run --rm -v dados-postgres:/dados -v $(pwd):/backup alpine \
tar czf /backup/dados-postgres.tar.gz -C /dados .El camino inverso al del backup.
docker run --rm -v dados-postgres:/dados -v $(pwd):/backup alpine \
tar xzf /backup/dados-postgres.tar.gz -C /dadosUn volumen no desaparece junto con el contenedor — hay que borrarlo a propósito.
docker volume rm dados-postgresCuidado: esto borra datos de verdad. Comprueba la lista antes.
docker volume ls -f dangling=true
docker volume prune-v borra los volúmenes anónimos de ese contenedor.
docker rm -v minha-appEl error clásico en Linux: el UID de dentro no es el mismo que el de fuera.
docker run -d --user $(id -u):$(id -g) -v $(pwd):/app minha-appCómo hablan los contenedores entre sí y con el mundo.
Toda instalación ya viene con bridge, host y none.
docker network lsRed propia del proyecto: los contenedores se ven por el nombre.
docker network create minha-redeAl crearlo o después, con el contenedor ya en marcha.
docker run -d --network minha-rede --name api minha-app
docker network connect minha-rede outro-containerDentro de la red, el nombre del contenedor es el host. No uses la IP.
# desde dentro del contenedor "api":
curl http://banco:5432
# en lugar de http://172.18.0.3:5432Muestra el rango de IP y quién está conectado.
docker network inspect minha-redeUna red en uso no se puede eliminar.
docker network disconnect minha-rede api
docker network rm minha-redeSin aislamiento de red: el contenedor usa la pila del host. Rápido, pero sin aislamiento (Linux).
docker run -d --network host nginxAislamiento total. Bueno para procesar datos no confiables.
docker run --rm --network none alpine ip addrLo expone solo en localhost, no en toda la red.
docker run -d -p 127.0.0.1:8080:80 nginxTambién se pueden exponer intervalos y otro protocolo.
docker run -d -p 8000-8010:8000-8010 minha-app
docker run -d -p 53:53/udp meu-dnshost.docker.internal resuelve a la máquina anfitriona.
curl http://host.docker.internal:5432Para cuando de verdad la necesitas.
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' apiUn contenedor desechable solo para diagnosticar la red.
docker run --rm --network minha-rede alpine ping -c 3 banco
docker run --rm --network minha-rede nicolaka/netshoot nslookup apiLimpieza de rutina.
docker network pruneVarios contenedores descritos en un archivo y levantados con un solo comando.
Describe servicios, puertos, variables y dependencias.
services:
api:
build: .
ports:
- "3000:3000"
environment:
DATABASE_URL: postgres://app:senha@banco:5432/loja
depends_on:
banco:
condition: service_healthy
banco:
image: postgres:16-alpine
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: senha
POSTGRES_DB: loja
volumes:
- dados:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app"]
interval: 5s
retries: 5
volumes:
dados:-d en segundo plano; sin él, los logs se quedan en el terminal.
docker compose up -dFuerza el rebuild de las imágenes antes de levantar.
docker compose up -d --builddown elimina contenedores y redes; con -v, borra también los volúmenes.
docker compose down
docker compose down -v # ¡borra los datos!El estado de cada servicio de la stack.
docker compose psTodos los servicios juntos, o uno concreto.
docker compose logs -f
docker compose logs -f api --tail 100exec en el contenedor en marcha; run crea uno nuevo.
docker compose exec api sh
docker compose run --rm api npm run migrateSin tocar el resto de la stack.
docker compose restart apiLevanta varias instancias del mismo servicio.
docker compose up -d --scale worker=3Muestra la configuración final, con las variables ya resueltas.
docker compose configUno base y uno por entorno — el segundo sobrescribe el primero.
docker compose -f compose.yaml -f compose.prod.yaml up -dCompose lee el .env del directorio automáticamente.
# .env
POSTGRES_PASSWORD=senha-forte
# compose.yaml
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}Servicios que solo se levantan cuando los pides — como las herramientas de desarrollo.
services:
adminer:
image: adminer
profiles: ["dev"]
# docker compose --profile dev up -dSin condition, Compose solo espera a que el contenedor arranque, no a que esté listo.
depends_on:
banco:
condition: service_healthyEvita que un servicio se coma la máquina entera.
deploy:
resources:
limits:
cpus: "1.5"
memory: 512MMantiene los contenedores para volver a encenderlos después.
docker compose stop
docker compose startActualiza las imágenes antes de levantar.
docker compose pullstats filtrado por los contenedores del proyecto.
docker compose top
docker stats $(docker compose ps -q)Lo que separa un contenedor que funciona en tu máquina de uno que aguanta producción.
Muestra cuánto están ocupando las imágenes, los contenedores, los volúmenes y la caché.
docker system df
docker system df -v-a elimina también las imágenes sin contenedor. Comprueba antes — --volumes borra datos.
docker system prune
docker system prune -a
docker system prune -a --volumesEl log de un contenedor crece sin límite y llena el disco del servidor. Este es el error nº 1 en producción.
# /etc/docker/daemon.json
{
"log-driver": "json-file",
"log-opts": { "max-size": "10m", "max-file": "3" }
}Contenedor root + volumen en el host = riesgo real de comprometer la máquina.
USER node # en el Dockerfile
docker run --user 1000:1000 minha-appSi la aplicación no necesita escribir, no la dejes.
docker run -d --read-only --tmpfs /tmp minha-appQuita privilegios del kernel que la aplicación no usa.
docker run -d --cap-drop ALL --cap-add NET_BIND_SERVICE nginxBloquea que un binario con setuid gane privilegios dentro del contenedor.
docker run -d --security-opt no-new-privileges minha-appEquivale a darle al contenedor el root del host. Solo en casos muy concretos y conscientes.
# evítalo
docker run --privileged minha-appMontar /var/run/docker.sock da el control total del host a quien esté en el contenedor.
# peligroso
docker run -v /var/run/docker.sock:/var/run/docker.sock minha-appDocker manda SIGTERM y espera 10s antes del SIGKILL. Tu aplicación tiene que tratar la señal.
docker stop -t 30 minha-app--init inserta un init mínimo que recoge los procesos huérfanos.
docker run -d --init minha-appMira el log de la ejecución anterior para encontrar la causa.
docker ps -a
docker logs --tail 200 minha-app
docker inspect -f '{{.State.ExitCode}} {{.State.Error}}' minha-appEl exit 137 casi siempre es falta de memoria.
docker inspect -f '{{.State.OOMKilled}}' minha-app
docker stats --no-stream minha-appDistroless no tiene shell: usa un contenedor auxiliar en el mismo namespace.
docker run --rm -it --pid container:minha-app --network container:minha-app \
nicolaka/netshoot shEjecútala como en producción antes de publicarla: sin bind mount, con las variables reales.
docker run --rm -p 3000:3000 --env-file .env.production minha-app:1.0Publica con una etiqueta de versión y despliega por digest — la garantía de que es exactamente esa imagen.
docker push usuario/app:1.4.2
docker inspect --format='{{index .RepoDigests 0}}' usuario/app:1.4.2Build y push dentro del pipeline, con caché entre ejecuciones.
docker buildx build --push \
--cache-from type=registry,ref=usuario/app:cache \
--cache-to type=registry,ref=usuario/app:cache,mode=max \
-t usuario/app:$GIT_SHA .Compose resuelve un servidor. Varios nodos, escalado automático y rolling updates piden un orquestador.
# Compose: 1 host, stack sencilla, equipo pequeño
# Kubernetes: múltiples nodos, autoscaling, despliegue sin downtime