Cheatsheet 03

Dockerfile

La receta para construir tu propia imagen. Cada instrucción genera una capa; el orden importa para la caché.

1

Instrucciones

Van en MAYÚSCULAS y se ejecutan de arriba hacia abajo.

Referencia rápida

InstrucciónQué haceEjemplo
FROMImagen base. Siempre la primera. AS nombre define una etapa.FROM node:18
WORKDIRCarpeta de trabajo dentro del contenedor. La crea si no existe (reemplaza a RUN mkdir -p + cd).WORKDIR /home/app
COPYCopia [ORIGEN-PC] → [DESTINO-CONTENEDOR]. El origen es relativo al build context.COPY . /home/app
ADDComo COPY pero además descomprime .tar y acepta URLs. Preferí COPY.ADD app.tar.gz /opt/
RUNEjecuta un comando al construir la imagen (instalar cosas).RUN npm i -g nodemon
ENVVariable de entorno disponible en build y en ejecución.ENV NODE_ENV=production
ARGVariable solo durante el build (--build-arg).ARG VERSION=1.0
EXPOSEDocumenta el puerto de la app. No publica nada (eso es -p).EXPOSE 3000
CMDComando por defecto al iniciar el contenedor. Solo vale el último.CMD ["node", "app.js"]
ENTRYPOINTEjecutable fijo del contenedor; CMD/args se le agregan.ENTRYPOINT ["dotnet", "Api.dll"]
USERUsuario con el que corre (evitar root en producción).USER node
i

RUN vs CMD: RUN se ejecuta una vez al construir la imagen. CMD se ejecuta cada vez que arranca un contenedor.

2

Node.js — imagen de producción

El código queda copiado dentro de la imagen.

Tu versión

Dockerfile (pruebas-docker)

Dockerfilefunciona ✓
# imagen a utilizar
FROM node:latest

# directorio en el contenedor donde va el código
RUN mkdir -p /home/app

# [ORIGEN-PC] [DESTINO-CONTAINER]
COPY . /home/app

# puerto de la app (informativo)
EXPOSE 3000

# comando + argumentos
CMD ["node", "/home/app/app.js"]
!

No instala dependencias: funciona porque copia tu node_modules local. Si tuvieras dependencias nativas (bcrypt, sharp…) compiladas en Windows, fallarían en Linux.

✕

En docs/Dockerfile-example.md tenés COPY E/Development/pruebas-docker /home/app: no funciona. COPY no acepta rutas absolutas de tu PC, solo rutas dentro del build context → COPY . /home/app.

Versión mejorada

Dockerfile recomendado

Dockerfileusa caché + reproducible
# versión fija: latest puede cambiar y romper el build
FROM node:18

# crea la carpeta y se para en ella
WORKDIR /home/app

# 1º solo las dependencias (capa cacheable)
COPY package*.json ./
RUN npm ci --omit=dev

# 2º el resto del código
COPY . .

EXPOSE 3000

CMD ["node", "app.js"]
  • WORKDIR reemplaza RUN mkdir -p y permite rutas relativas
  • Si solo cambiás código, npm ci no se vuelve a ejecutar (ver caché)
  • Acompañalo con un .dockerignore que excluya node_modules
3

Node.js — imagen de desarrollo

No copia el código: lo recibe por un volumen (bind mount) desde compose y nodemon recarga al guardar.

Dockerfile.dev

Dockerfile.dev
FROM node:latest

# nodemon global para recargar al guardar
RUN npm i -g nodemon
RUN mkdir -p /home/app

WORKDIR /home/app

EXPOSE 3000

# -L = legacy watch (polling)
CMD ["nodemon", "-L", "app.js"]

Por qué así

  • Sin COPY: el código entra por volumes: - .:/home/app en docker-compose-dev.yml
  • Editás en tu PC → el archivo cambia dentro del contenedor → nodemon reinicia
  • No hace falta rebuild por cada cambio de código (solo si cambia el Dockerfile o dependencias globales)
  • -L es necesario en Windows/WSL: los eventos de cambio de archivo no cruzan el bind mount, así que nodemon tiene que consultar (polling)
  • RUN mkdir -p sobra: WORKDIR ya crea la carpeta
✓

Levantalo con docker compose -f docker-compose-dev.yml up.

4

Capas y caché

Si una capa cambia, se reconstruyen esa y todas las de abajo. Lo que cambia poco va arriba.

Cambiaste solo app.js y volvés a hacer docker build: FROM node:18 CACHED WORKDIR /home/app CACHED COPY package*.json ./ CACHED RUN npm ci CACHED · ahorra minutos COPY . . cambió → se rehace CMD ["node","app.js"] se rehace Con el orden “todo junto”: FROM node:18 CACHED WORKDIR /home/app CACHED COPY . . cambió → se rehace RUN npm ci reinstala TODO otra vez ✕ CMD ["node","app.js"] se rehace copiar package.json primero = builds mucho más rápidos
5

Multi-stage build (ASP.NET Core / .NET 10)

Una etapa con el SDK para compilar, otra solo con el runtime para ejecutar. La imagen final es mucho más liviana.

aspnet-core/ build context .sln · Domain Application · Host Etapa 1 · build dotnet/sdk:10.0 WORKDIR /src COPY . . RUN dotnet restore RUN dotnet publish -o /app/publish /app/publish ⟶ se descarta (no va en la imagen final) Etapa 2 · runtime dotnet/aspnet:10.0 WORKDIR /app COPY --from=build /app/publish . EXPOSE 8080 ENTRYPOINT dotnet Api.dll = imagen final solo runtime + DLLs compiladas --from=build

Dockerfile

src/<Proyecto>.HttpApi.Host/Dockerfile
# ---------- Etapa 1: compilar ----------
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src

# toda la solución: Host depende de Domain, Application…
COPY . .

RUN dotnet restore "src/<Proyecto>.HttpApi.Host/<Proyecto>.HttpApi.Host.csproj"
RUN dotnet publish "src/<Proyecto>.HttpApi.Host/<Proyecto>.HttpApi.Host.csproj" \
    -c Release -o /app/publish

# ---------- Etapa 2: ejecutar ----------
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime
WORKDIR /app
COPY --from=build /app/publish .

EXPOSE 8080
ENTRYPOINT ["dotnet", "<Proyecto>.HttpApi.Host.dll"]

Paso a paso (de tus notas)

  1. Etapa build con la imagen del SDK de .NET 10.
  2. WORKDIR /src: nos paramos en una carpeta nueva del contenedor.
  3. COPY . . copia todo el build context (la carpeta aspnet-core completa, así se definió en compose) porque HttpApi.Host necesita los otros proyectos para compilar.
  4. restore + publish. El resultado va a /app/publish — ruta absoluta, no relativa a /src.
  5. Etapa runtime: solo lo necesario para ejecutar.
  6. WORKDIR /app.
  7. COPY --from=build trae lo publicado desde la etapa anterior.
  8. EXPOSE 8080 (informativo; en .NET 8+ el puerto por defecto es 8080).
  9. ENTRYPOINT ejecuta la DLL compilada.
!

El restore separado no aprovecha la caché tal como está. Para que sirva, copiá primero solo los .csproj, hacé restore y recién después COPY . . (mismo truco que package.json en Node). publish igual hace restore si hace falta.

6

CMD vs ENTRYPOINT · forma exec vs shell

Cómo arranca el proceso principal.

CMD

Comando por defecto. Se reemplaza si pasás algo después de la imagen.

CMD ["node", "app.js"]
docker run miapp:1 node otro.js   # reemplaza el CMD

ENTRYPOINT

Ejecutable fijo. Lo que pases (o el CMD) se agrega como argumentos.

ENTRYPOINT ["dotnet", "Api.dll"]
docker run api:1 --urls http://+:5000   # dotnet Api.dll --urls ...

Exec form vs shell form

Usá la forma JSON (exec): el proceso recibe las señales y docker stop lo cierra limpio.

# ✓ exec form
CMD ["node", "app.js"]
# ✕ shell form (corre dentro de /bin/sh -c)
CMD node app.js
7

.dockerignore

Lo que no se manda al build context. Hace los builds más rápidos y evita copiar basura o secretos.

Node.js

.dockerignorejunto al Dockerfile
node_modules
npm-debug.log
.git
.gitignore
.env
docs
Dockerfile*
docker-compose*.yml

.NET

.dockerignoreen la raíz del context
**/bin
**/obj
**/.vs
**/node_modules
.git
*.user
**/appsettings.Development.json
i

Tu proyecto pruebas-docker no tiene .dockerignore: el COPY . está copiando node_modules y .git dentro de la imagen.

8

Construir la imagen

Desde la carpeta donde está el Dockerfile.

Comandos de build

docker build -t miapp:1 .
Build normal con tag miapp:1 y contexto .
docker build -t miapp:dev -f Dockerfile.dev .
Con otro Dockerfile.
docker build --no-cache -t miapp:1 .
Ignorar la caché de capas.
docker build --target build -t api:build .
Construir solo hasta una etapa (multi-stage).
docker build --build-arg VERSION=2.0 -t miapp:2 .
Pasar valores a un ARG.
docker build -t api:1 -f src/Api/Dockerfile .
Dockerfile en subcarpeta, contexto en la raíz (caso .NET).