Docker Compose: build vs image en un VPS
image descarga una etiqueta publicada y build crea la imagen localmente. Vea por qué compose up ignora cambios en Dockerfile y cómo corregirlo.
Docker Compose: build frente a image, respuesta breve
En un archivo de Docker Compose, image: especifica una imagen que se debe descargar de un registro, mientras que build: indica a Compose que debe crear una imagen en este equipo a partir de un Dockerfile. Si se define sólo image:, Compose descarga esa etiqueta y la ejecuta. Si se define sólo build:, Compose crea la imagen aquí y le asigna un nombre derivado del nombre del proyecto y del servicio. Si se definen ambos, Compose crea la imagen localmente y etiqueta el resultado con el nombre especificado en image:. Así se crea una imagen y se publica con el nombre elegido.
Esa es toda la diferencia. A continuación se explica cómo funciona en la práctica en un servidor. Se da por hecho que Docker Engine y el plugin de Compose ya están instalados; ejecutar Docker en un VPS cubre esa parte.
Las tres formas completas
Extraiga una etiqueta publicada y ejecútela. No interviene ningún Dockerfile en ningún momento.
services:
web:
image: nginx:1.27
restart: unless-stopped
ports:
- "80:80"Compile a partir de un Dockerfile del directorio actual. No se extrae nada salvo la imagen base indicada en FROM.
services:
web:
build: .
restart: unless-stopped
ports:
- "80:80"Compile localmente y etiquete el resultado. docker compose push puede enviar después esa etiqueta exacta a un registro.
services:
web:
build:
context: .
dockerfile: Dockerfile
image: registry.example.com/acme/web:1.4.2
restart: unless-stopped
ports:
- "80:80"context es el directorio que se envía al constructor. dockerfile se resuelve en relación con ese contexto, por lo que context: . con dockerfile: docker/prod.Dockerfile es normal y correcto. Ejecute docker compose images para ver el nombre y el ID de imagen asociados a cada contenedor de servicio. Es la forma más rápida de confirmar cuál de estas tres formas escribió realmente.
¿Por qué docker compose up no vuelve a crear la imagen después de cambiar el Dockerfile?
Porque up comprueba si la imagen existe, no si está actualizada.
Cuando Compose inicia un servicio que tiene una sección build:, busca la imagen en el almacén local de imágenes. Si ya existe una imagen con ese nombre, Compose la utiliza. No lee el Dockerfile, no compara los archivos de origen ni consulta ninguna marca de tiempo. La especificación de Compose define esta regla mediante el atributo pull_policy, y el comportamiento predeterminado sólo crea una imagen cuando no existe. Si está presente, se considera suficiente.
Por eso edita app.py, ejecuta docker compose up -d, ve que Compose informa de que el contenedor está en ejecución y sirve el código antiguo. No se produjo ningún error, así que no apareció ningún aviso. Este es el caso más habitual cuando se informa de que «el cambio no se aplicó» con Compose. La pista está en la palabra de estado que Compose muestra junto al nombre del contenedor: un contenedor que Compose sustituyó aparece como recreated o started, mientras que un contenedor que Compose decidió no modificar aparece como running.
Dos comprobaciones permiten confirmarlo. docker compose images muestra el ID de imagen que utiliza cada contenedor; anótelo antes del despliegue y compárelo después. docker image ls incluye una columna CREATED, y una imagen creada antes de su último commit está obsoleta, independientemente de lo que haya mostrado el script de despliegue.
Qué flags fuerzan una recompilación
docker compose up -d --buildcompila primero y después vuelve a crear cualquier contenedor cuya imagen haya cambiado. Este es el flag que busca la mayoría de los usuarios.docker compose build webcompila un servicio y no inicia nada. Combínelo condocker compose up --no-deps -d webpara sustituir sólo ese contenedor y mantener el resto de la pila en ejecución.docker compose build --no-cache webdescarta todas las capas almacenadas en caché y compila desde la primera instrucción.docker compose build --pullintenta descargar una versión más reciente de la imagen base enFROM. Así, un tag variable comonode:22obtiene su contenido actual en lugar de la copia que descargó en marzo.docker compose up -d --force-recreatevuelve a crear los contenedores a partir de la imagen que ya utilizan. Nunca compila. Usar este flag cuando quería usar--buildes un error frecuente.
También puede trasladar esta decisión al archivo. Según la especificación de Compose, pull_policy: build significa que Compose compila la imagen y vuelve a compilarla si ya existe. Cada up implica entonces una compilación, lo que resulta adecuado en un portátil y rara vez en un servidor.
services:
web:
build: .
image: registry.example.com/acme/web:dev
pull_policy: buildTambién conviene conocer otra interacción. docker compose pull intenta descargar imágenes para los servicios que también tienen una sección build. Si esa descarga falla, indica que la imagen debe compilarse. Pase --ignore-buildable para omitir esos servicios sin mostrar mensajes.
Cómo decide la caché de compilación el tiempo de despliegue
Cada instrucción de un Dockerfile produce una capa, y el constructor reutiliza una capa en caché cuando esa instrucción y sus entradas no han cambiado. Para COPY, las entradas son el contenido de los archivos que se copian. Cuando una capa deja de coincidir con la caché, se reconstruyen todas las capas posteriores, porque cada capa se construye sobre el sistema de archivos que produjo la anterior.
Esta regla determina si el despliegue tarda segundos o minutos. Ordene el Dockerfile desde lo que cambia con poca frecuencia hasta lo que cambia en cada commit.
FROM node:22-slim
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
CMD ["node", "server.js"]npm ci se encuentra antes que COPY . ., por lo que editar un archivo de código fuente deja la capa de instalación en caché y la compilación continúa desde el paso de copia. Si intercambia esas dos líneas, un cambio de un solo carácter vuelve a instalar todas las dependencias, porque COPY . . invalida la capa sobre la que se construye npm ci. La misma estructura se aplica a pip install -r requirements.txt y a go mod download.
--no-cache es la herramienta adecuada cuando sospecha que una capa obsoleta está ocultando su corrección. No es una buena opción predeterminada, porque elimina la reutilización que el orden del Dockerfile permite aprovechar.
Hay un elemento que la imagen establece y que Compose puede sobrescribir: CMD del Dockerfile define lo que la imagen ejecuta de forma predeterminada, y una clave command: en el servicio lo reemplaza. Cómo interactúan command y entrypoint es importante aquí, porque una sobrescritura de Compose puede hacer que una imagen recién compilada se comporte exactamente como la anterior.
Contexto de compilación y .dockerignore
context: . significa que Compose empaqueta ese directorio y lo envía al compilador antes de ejecutar la primera instrucción. Se incluye todo lo que contiene, incluidos .git y cualquier directorio de datos que mantenga junto al código fuente. Si una compilación se detiene en el paso de transferencia del contexto en un proyecto que no ha cambiado, el contexto es demasiado grande.
Un archivo .dockerignore en la raíz del contexto excluye rutas de esa transferencia. La sintaxis es similar a .gitignore.
.git
node_modules
*.log
data/
.envHay dos ventajas. La transferencia es menor, por lo que cada compilación empieza más rápido. Además, COPY . . ya no puede copiar .env en la imagen, donde cualquiera que descargue esa imagen puede leerlo.
El caso de compilaciones lentas que empeoran con el tiempo suele ser un montaje bind. Un volumen con nombre reside fuera del directorio del proyecto, pero un montaje bind como ./data:/var/lib/postgresql/data se encuentra dentro del contexto de compilación. Por eso, las compilaciones se vuelven más lentas cada semana a medida que crece la base de datos. Una línea en .dockerignore lo corrige. Montajes bind frente a volúmenes con nombre explica el equilibrio general.
Los argumentos de compilación presentan una versión menor del mismo riesgo. Los valores que se pasan mediante args: aparecen en el historial de la imagen para cualquiera que tenga esa imagen. Por tanto, incluya ahí un número de versión y nunca un token. Archivos de entorno y secretos en Compose explica dónde deben almacenarse las credenciales.
¿Debe compilar en el VPS o compilar en otro lugar y descargar la imagen?
Compilar en el equipo que sirve el tráfico es la opción predeterminada porque es el camino más corto: git pull y después docker compose up -d --build. Esto es adecuado en un servidor pequeño del que todavía no depende nadie. Deja de serlo por dos motivos que puede medir y por otro que sólo aparece en un día problemático.
Memoria. Una compilación ejecuta compiladores y empaquetadores junto a la aplicación en producción, y estos suelen ser los procesos que más memoria consumen. En un VPS de 1 GB, un empaquetador de JavaScript o una compilación de Rust suele ser el proceso más grande del equipo. Cuando el kernel se queda sin memoria, termina el proceso más grande: la compilación se detiene con Killed y el estado de salida 137, o se termina la base de datos y el sitio deja de funcionar durante el despliegue. dmesg -T | grep -i oom muestra la línea de terminación con el nombre del proceso, así que puede saber cuál de los dos casos ocurrió en lugar de hacer suposiciones.
Disco. Cada compilación deja capas, y el compilador mantiene su propia caché separada de las imágenes. docker system df muestra ambas cosas, y la fila de la caché de compilación sólo crece. Recupere espacio con docker image prune para las imágenes sin referencia y con docker builder prune para las capas almacenadas en caché. Un disco lleno detiene más que la compilación. La base de datos también deja de escribir, y ese fallo cuesta mucho más que un despliegue lento.
Reproducibilidad. Una imagen compilada en el servidor sólo existe en ese servidor. Para revertirla debe cambiar a la versión anterior y volver a compilar, pero esa compilación no garantiza producir lo mismo, porque la etiqueta base cambió y los mirrors de paquetes también. Compilar en otro lugar y enviar una etiqueta convierte la reversión en una edición: apunte image: a la etiqueta anterior y ejecute docker compose up -d.
La configuración que funciona de forma fiable es sencilla. La integración continua ejecuta la compilación y publica registry.example.com/acme/web:<git-sha>, y el archivo Compose del VPS contiene image: sin ninguna clave build:.
docker compose pull
docker compose up -dEjecute docker login registry.example.com una vez en el servidor y Compose podrá descargar etiquetas privadas a partir de entonces.
Conserve la sección de compilación para el desarrollo en lugar de eliminarla, en un archivo cuyo nombre elija.
# compose.dev.yaml
services:
web:
build:
context: .
pull_policy: builddocker compose -f compose.yaml -f compose.dev.yaml up -d --buildAsigne a ese archivo el nombre compose.dev.yaml y no compose.override.yaml. Compose carga automáticamente un archivo de sustituciones cuando está presente, por lo que una sustitución copiada por error en el servidor volvería a iniciar las compilaciones allí sin avisar. Combinar varios archivos Compose explica cómo se resuelve cada clave durante la combinación.
La trampa de la arquitectura al compilar en otro equipo
Una imagen incluye la arquitectura de CPU para la que se compiló. Si compila en un portátil con Apple Silicon, sube la imagen y después descarga esa etiqueta en un VPS x86_64, Docker advierte de que la plataforma solicitada para la imagen no coincide con la plataforma detectada en el host. El proceso termina con exec format error. El mensaje parece indicar que el binario está dañado, pero no es así. Compile explícitamente para el destino:
docker buildx build --platform linux/amd64 \
-t registry.example.com/acme/web:1.4.2 --push .El mismo desajuste ocurre en sentido inverso si su portátil usa x86 y ejecuta un VPS ARM en lugar de uno x86. Si deja que CI compile para la arquitectura en la que realiza el despliegue, elimina esta incertidumbre.
Qué comprobar después de un despliegue
docker compose imagesmuestra la imagen y la etiqueta que usa cada contenedor en ejecución. Un ID de imagen diferente confirma que la nueva compilación está en servicio.docker compose configmuestra el archivo combinado después de sustituir las variables, para que pueda leer el nombre de imagen final que usará Compose antes de ejecutar nada.docker compose logs -f webdurante los primeros 30 segundos después del cambio. Un contenedor que se inicia y termina entra en un bucle de reinicios en lugar de permanecer activo, y el bucle pasa desapercibido si no lo supervisa.docker image lsmuestra una columna CREATED. Una imagen anterior a su último commit nunca se volvió a compilar.
Si todavía está preparando el archivo que usarán estas comprobaciones, los conceptos básicos de un archivo de Compose en un VPS explica las claves relacionadas, y la guía rápida de comandos de Compose incluye el resto de subcomandos.
FAQ
¿Puedo usar build e image en el mismo servicio?
Sí. Es la configuración habitual para un proyecto que compila usted mismo. Compose compila desde la sección build: y etiqueta el resultado con el valor de image:. Esa etiqueta es la que docker compose push envía a un registro y la que otra máquina descarga. Sin una clave image:, Compose sigue compilando, pero asigna a la imagen el nombre del proyecto y del servicio, y advierte de que falta el atributo necesario para enviar la imagen.
¿Por qué docker compose up no detecta los cambios de mi Dockerfile?
Porque up sólo comprueba si existe una imagen con ese nombre. Si existe, Compose la inicia y nunca la compara con su Dockerfile ni con los archivos de origen. Ejecute docker compose up -d --build, o ejecute docker compose build web seguido de docker compose up --no-deps -d web para reemplazar un único servicio. Al establecer pull_policy: build en el servicio, cada up vuelve a compilar la imagen, lo que resulta adecuado en una máquina de desarrollo.
¿Cuál es la diferencia entre --build y --force-recreate?
--build vuelve a compilar la imagen y después recrea los contenedores cuya imagen haya cambiado. --force-recreate recrea los contenedores a partir de la imagen que ya tienen, por lo que nunca puede incorporar un cambio de código. Si el cambio está en el código fuente o en el Dockerfile, --build es la opción que debe usar. --force-recreate sirve para restablecer el propio contenedor, por ejemplo, para borrar su capa escribible y conservar la misma imagen.
¿Debo compilar mis imágenes de Docker en el VPS o en otro lugar?
Compílelas en otro lugar y descargue una etiqueta cuando el servidor también atienda tráfico. La compilación compite con la aplicación por la memoria. En un VPS pequeño, el kernel resuelve esa falta de memoria terminando el proceso más grande, que puede ser la compilación o la base de datos. Las compilaciones también dejan en el disco una caché que ningún proceso limpia automáticamente. Compilar en el servidor sigue siendo adecuado para un proyecto pequeño sin usuarios. Migrar más adelante cuesta poco si mantiene la sección build: en un archivo Compose exclusivo para desarrollo.
¿Cómo evito que la caché de compilación de Docker llene el disco?
Ejecute docker system df para ver cuánto espacio ocupan las imágenes y la caché de compilación. docker builder prune elimina las capas almacenadas en caché y docker image prune elimina las imágenes huérfanas que dejaron las compilaciones anteriores. Añadir -a a cualquiera de los dos comandos aplica una limpieza más agresiva y obliga a que la siguiente compilación empiece sin caché. No programe docker system prune -af --volumes en un servidor, porque --volumes elimina cualquier volumen que no esté usando actualmente ningún contenedor. Una pila detenida para mantenimiento mantiene la base de datos exactamente en un volumen de ese tipo.