# Compilaciones multi-etapa


Las compilaciones multi-etapa (multi-stage builds) son útiles para cualquiera que haya tenido dificultades para optimizar
sus Dockerfiles manteniéndolos al mismo tiempo fáciles de leer y mantener.

## Usar compilaciones multi-etapa

Con las compilaciones multi-etapa, utilizas múltiples instrucciones `FROM` en tu Dockerfile.
Cada instrucción `FROM` puede usar una base diferente, y cada una de ellas comienza una nueva
etapa de la compilación. Puedes copiar selectivamente artefactos de una etapa a
otra, dejando atrás todo lo que no quieras en la imagen final.

El siguiente Dockerfile tiene dos etapas separadas: una para compilar un binario
y otra donde el binario se copia de la primera etapa a la siguiente.

```dockerfile
# syntax=docker/dockerfile:1
FROM golang:1.25
WORKDIR /src
COPY <<EOF ./main.go
package main

import "fmt"

func main() {
  fmt.Println("hello, world")
}
EOF
RUN go build -o /bin/hello ./main.go

FROM scratch
COPY --from=0 /bin/hello /bin/hello
CMD ["/bin/hello"]
```

Solo necesitas un único Dockerfile. No es necesario un script de compilación separado. Simplemente
ejecuta `docker build`.

```console
$ docker build -t hello .
```

El resultado final es una imagen de producción diminuta que no contiene más que el binario en su interior.
Ninguna de las herramientas de compilación requeridas para compilar la aplicación se incluye en la
imagen resultante.

¿Cómo funciona? La segunda instrucción `FROM` comienza una nueva etapa de compilación con
la imagen `scratch` como base. La línea `COPY --from=0` copia únicamente el
artefacto compilado de la etapa anterior a esta nueva etapa. El SDK de Go y cualquier
artefacto intermedio se dejan atrás y no se guardan en la imagen final.

## Nombrar tus etapas de compilación

Por defecto, las etapas no tienen nombre y te refieres a ellas por su número entero,
comenzando con el 0 para la primera instrucción `FROM`. Sin embargo, puedes
nombrar tus etapas añadiendo un `AS <NOMBRE>` a la instrucción `FROM`. Este
ejemplo mejora el anterior al nombrar las etapas y usar el nombre en la
instrucción `COPY`. Esto significa que incluso si las instrucciones en tu
Dockerfile se reordenan más adelante, el comando `COPY` no se romperá.

```dockerfile
# syntax=docker/dockerfile:1
FROM golang:1.25 AS build
WORKDIR /src
COPY <<EOF /src/main.go
package main

import "fmt"

func main() {
  fmt.Println("hello, world")
}
EOF
RUN go build -o /bin/hello ./main.go

FROM scratch
COPY --from=build /bin/hello /bin/hello
CMD ["/bin/hello"]
```

## Detenerse en una etapa de compilación específica

Cuando compilas tu imagen, no necesitas necesariamente compilar todo el
Dockerfile incluyendo cada etapa. Puedes especificar una etapa de compilación de destino. El
siguiente comando asume que estás utilizando el `Dockerfile` anterior pero se detiene en
la etapa llamada `build`:

```console
$ docker build --target build -t hello .
```

Algunos escenarios donde esto podría ser útil son:

- Depurar una etapa de compilación específica
- Usar una etapa de `debug` con todos los símbolos o herramientas de depuración habilitados, y una
  etapa de `production` más ligera
- Usar una etapa de `testing` en la que tu aplicación se llene con datos de prueba, pero
  compilando para producción usando una etapa diferente que utiliza datos reales

## Usar una imagen externa como etapa

Al utilizar compilaciones multi-etapa, no estás limitado a copiar de las etapas que
creaste previamente en tu Dockerfile. Puedes usar la instrucción `COPY --from` para
copiar desde una imagen separada, ya sea utilizando el nombre de la imagen local, una etiqueta disponible
localmente o en un registro de Docker, o un ID de etiqueta. El cliente de Docker descargará la imagen
si es necesario y copiará el artefacto desde allí. La sintaxis es:

```dockerfile
COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf
```

## Usar una etapa anterior como una nueva etapa

Puedes retomar el trabajo donde lo dejó una etapa anterior haciendo referencia a ella al utilizar
la directiva `FROM`. Por ejemplo:

```dockerfile
# syntax=docker/dockerfile:1

FROM alpine:latest AS builder
RUN apk --no-cache add build-base

FROM builder AS build1
COPY source1.cpp source.cpp
RUN g++ -o /binary source.cpp

FROM builder AS build2
COPY source2.cpp source.cpp
RUN g++ -o /binary source.cpp
```

## Diferencias entre el builder heredado y BuildKit

El builder heredado de Docker Engine procesa todas las etapas de un Dockerfile que conducen
al `--target` seleccionado. Compilará una etapa incluso si el destino
seleccionado no depende de esa etapa.

[BuildKit](/build/buildkit/) solo compila las etapas de las que depende la etapa de
destino.

Por ejemplo, dado el siguiente Dockerfile:

```dockerfile
# syntax=docker/dockerfile:1
FROM ubuntu AS base
RUN echo "base"

FROM base AS stage1
RUN echo "stage1"

FROM base AS stage2
RUN echo "stage2"
```

Con [BuildKit habilitado](/build/buildkit/#getting-started), compilar el
destino `stage2` en este Dockerfile significa que solo se procesan `base` y `stage2`.
No hay dependencia de `stage1`, por lo que se omite.

```console
$ DOCKER_BUILDKIT=1 docker build --no-cache -f Dockerfile --target stage2 .
[+] Building 0.4s (7/7) FINISHED                                                                    
 => [internal] load build definition from Dockerfile                                            0.0s
 => => transferring dockerfile: 36B                                                             0.0s
 => [internal] load .dockerignore                                                               0.0s
 => => transferring context: 2B                                                                 0.0s
 => [internal] load metadata for docker.io/library/ubuntu:latest                                0.0s
 => CACHED [base 1/2] FROM docker.io/library/ubuntu                                             0.0s
 => [base 2/2] RUN echo "base"                                                                  0.1s
 => [stage2 1/1] RUN echo "stage2"                                                              0.2s
 => exporting to image                                                                          0.0s
 => => exporting layers                                                                         0.0s
 => => writing image sha256:f55003b607cef37614f607f0728e6fd4d113a4bf7ef12210da338c716f2cfd15    0.0s
```

Por otro lado, compilar el mismo destino sin BuildKit da como resultado que se procesen
todas las etapas:

```console
$ DOCKER_BUILDKIT=0 docker build --no-cache -f Dockerfile --target stage2 .
Sending build context to Docker daemon  219.1kB
Step 1/6 : FROM ubuntu AS base
 ---> a7870fd478f4
Step 2/6 : RUN echo "base"
 ---> Running in e850d0e42eca
base
Removing intermediate container e850d0e42eca
 ---> d9f69f23cac8
Step 3/6 : FROM base AS stage1
 ---> d9f69f23cac8
Step 4/6 : RUN echo "stage1"
 ---> Running in 758ba6c1a9a3
stage1
Removing intermediate container 758ba6c1a9a3
 ---> 396baa55b8c3
Step 5/6 : FROM base AS stage2
 ---> d9f69f23cac8
Step 6/6 : RUN echo "stage2"
 ---> Running in bbc025b93175
stage2
Removing intermediate container bbc025b93175
 ---> 09fc3770a9c4
Successfully built 09fc3770a9c4
```

El builder heredado procesa `stage1`, incluso si `stage2` no depende de él.


