ARG vs ENV en un agente IA: build-time no es runtime
Resumen
Cómo no hornear el token ni perder NODE_ENV: ARG es --build-arg y no vive en el contenedor; ENV sí persiste y docker inspect lo muestra. Scope por stage, ENV pisa ARG del mismo nombre, cache miss en el primer uso. Distinto de build secrets y de NODE_ENV en producción.

Qué resuelve
Esta pieza se queda en la decisión práctica: qué instalar, qué riesgo agrega y cómo aplicarlo sin romper operación.
ARG NPM_TOKEN parece “solo de build”. docker history y las attestations mode=max lo imprimen igual. ENV TELEGRAM_TOKEN=... parece “configuración”: docker inspect y cualquier docker run lo leen. En un agente, esas dos instrucciones resuelven problemas distintos. Mezclarlas es el atajo que filtra el bot a GHCR.
No es build secrets (eso es el mount de BuildKit). No es NODE_ENV en producción (eso es qué valor pones en runtime). Aquí es cuándo una variable existe: durante docker build, o dentro del proceso que atiende el webhook. Hub: seguridad, coste y operación.
La regla
ARG declara un valor que el cliente pasa con --build-arg. Vive desde la línea donde lo declaras. Llega a los RUN siguientes como variable de entorno de build. No se embebe en la imagen final. ENV sí: queda en el config de la imagen, sobrevive al docker run y docker inspect la lista. docker run --env KEY=valor la pisa.
La referencia oficial (consultada el 7 de septiembre de 2026) lo deja en una frase: Unlike ENV, an ARG variable is not embedded in the image and is not available in the final container. El mismo documento advierte no usar ARG para credenciales: salen en docker history y en provenance max si el repo de Actions es público.
Qué hace cada instrucción
ARG | ENV | |
|---|---|---|
| Cuándo existe | Desde su línea, solo en el build | Build y contenedor |
| Cómo se pasa | --build-arg NAME=value | Dockerfile, Compose environment, docker run -e |
Visible en inspect | No (el valor) | Sí |
Visible en history | Sí, si lo usas | Sí, el layer de ENV |
| Para un token | Nunca | Nunca en la imagen; runtime por Compose secrets |
Para NODE_ENV | No: el proceso no lo ve | Runtime stage, no en el builder |
Sintaxis:
ARG NODE_VERSION=22
FROM node:${NODE_VERSION}-alpine AS build
ARG APP_REVISION
ENV APP_REVISION=${APP_REVISION:-dev}
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
COPY src ./src
RUN pnpm build
FROM node:${NODE_VERSION}-alpine AS runner
ARG NODE_VERSION
ENV NODE_ENV=production
WORKDIR /app
COPY --from=build /app/dist ./dist
USER node
CMD ["node", "dist/server.js"]
FROM solo puede ir precedido de ARG globales (y directivas). El ARG NODE_VERSION antes del primer FROM sirve para interpolar la base. Dentro de cada stage hay que volver a declararlo si lo usas: un ARG de un stage no atraviesa stages no derivados. Eso está en el apartado de Scope de ARG y en multi-stage.
Scope: la línea importa
Este ejemplo de la docs no es teórico:
FROM busybox
USER ${username:-some_user}
ARG username
USER $username
Con --build-arg username=what_user, el primer USER cae al fallback some_user porque username aún no está declarado. Antes de ARG, la expansión es cadena vacía (o el default de ${var:-...}). El segundo USER ya ve what_user.
Para el agente: no pongas ENV NODE_ENV=$NODE_ENV antes de ARG NODE_ENV. Quedas con vacío persistido en la imagen. Declara, luego asigna.
ENV de un stage padre sí se hereda en stages FROM de ese padre. ARG de un stage no relacionado, no. Si el builder y el runner no comparten base con el ARG, repite ARG en el runner o no esperes $APP_REVISION ahí.
ENV siempre gana al ARG del mismo nombre
La docs lo demuestran:
FROM ubuntu
ARG CONT_IMG_VER
ENV CONT_IMG_VER=v1.0.0
RUN echo $CONT_IMG_VER
--build-arg CONT_IMG_VER=v2.0.1 no cambia el RUN: imprime v1.0.0. Un ENV posterior pisa el ARG. El patrón útil es el contrario: persistir a propósito un build-arg:
ARG APP_REVISION
ENV APP_REVISION=${APP_REVISION:-dev}
Sin --build-arg, el contenedor ve dev. Con él, ves el SHA en logs y en /health. Eso sí quieres en runtime. Un token, no.
La forma ENV KEY value (sin =) está deprecada en espíritu: ENV ONE TWO= THREE=world define una variable ONE con valor TWO= THREE=world. Usa siempre KEY=value.
No uses ENV para un flag de un solo RUN
ENV DEBIAN_FRONTEND=noninteractive cambia apt-get para todo el que use tu imagen, no solo el build. La docs lo marcan como efecto colateral. Para un RUN apt-get:
RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -y --no-install-recommends ca-certificates
o un ARG DEBIAN_FRONTEND=noninteractive que no sobrevive al contenedor. Un agente no necesita ese ENV en producción.

Cache: el miss no está en ARG, está en el primer uso
Declarar ARG CONT_IMG_VER no invalida cache. El primer RUN (o ENV que lo interpola) sí. Todo RUN después de un ARG recibe esa variable como entorno de build, aunque el comando no la mencione. Cambiar --build-arg en CI porque “es el SHA” puede romper el cache de pnpm install si el ARG está arriba del install.
Orden de un agente:
ARGde versión de imagen /FROM(arriba del todo).- Lockfile +
pnpm installsin ARG de revisión. ARG APP_REVISIONjusto antes delENV APP_REVISION=...o del label, nunca antes de las deps.
Los ARG predefinidos de proxy (HTTP_PROXY, http_proxy, HTTPS_PROXY, …) no salen en history ni invalidan cache salvo que los declares con ARG HTTP_PROXY en el Dockerfile. Si los declaras, el password del proxy queda en history. No lo declares.
BuildKit inyecta TARGETPLATFORM, TARGETOS, TARGETARCH, BUILDPLATFORM, etc. en scope global. Dentro del stage no existen hasta ARG TARGETPLATFORM sin valor. Redecláralo en el stage que hace RUN echo $TARGETARCH.
Secretos: ARG no es un secreto
La advertencia oficial: no pases credenciales por --build-arg. Salen en history y en attestations max del workflow de Buildx en repos públicos. El camino es RUN --mount=type=secret + --secret id=..., cubierto en build secrets. El token de runtime (Telegram, Slack) no va ni en ARG ni en ENV de la imagen: Compose secrets o la variable del host. Qué es un secreto y cómo rotarlo: secretos y variables. Y el .env fuera del contexto: dockerignore.
Qué poner en cada capa de un agente
| Dato | Dónde |
|---|---|
Tag de Node (22) | ARG global + FROM |
pnpm-lock.yaml hash | no es variable; es el archivo |
SHA / APP_REVISION | ARG tarde → ENV si el proceso lo lee |
NODE_ENV=production | ENV en runner, no en el stage de pnpm build si el build de Next lo quiere unset |
TELEGRAM_TOKEN | secret de runtime, nunca imagen |
NPM_TOKEN de registry privado | secret mount de build |
| Proxy corporativo | --build-arg HTTPS_PROXY=... sin ARG HTTPS_PROXY en el Dockerfile |
Compose environment: pisa el ENV de la imagen. Bien si es consciente. Un env_file: .env de laptop con NODE_ENV=development lo echa a perder en prod: no montes ese file.
Checklist
- Ningún token en
ARGni enENVde la imagen. -
ARGde revisión después depnpm install. - Cada stage que interpola un ARG lo declara otra vez.
-
ENV KEY=value, nuncaENV KEY value. -
NODE_ENV=productionsolo en el runner. -
docker historyydocker inspectno muestran tokens. -
--build-argde proxy sinARG HTTP_PROXYen el archivo.
Curso: instalar un agente.
FAQ
¿Por qué echo $APP_REVISION en el contenedor sale vacío? Usaste ARG y no lo copiaste a ENV. ARG no existe en runtime.
¿Por qué el --build-arg no cambia nada? Hay un ENV NAME=fijo del mismo nombre después. ENV gana.
¿Por qué el cache de install se rompe en cada commit? El ARG APP_REVISION está encima del RUN pnpm install. Bájalo.
¿ARG es seguro porque “no queda en la imagen”? No. Queda en history y en provenance. Secretos = mount.
¿Puedo usar ARG para NODE_ENV? El proceso del webhook no lo ve. ENV en el runner, o -e en Compose.

Lecturas relacionadas
Sigue explorando Deploy y otras piezas para builders.

Build secrets Docker para un agente IA: el token no va en el layer

Trivy para tu agente IA: escanear la imagen Docker antes de desplegar

Buildx multi-platform para un agente IA: amd64 y arm64 en un tag
