# Crear y construir una Docker Hardened Image


Las Docker Hardened Images (DHI) se construyen a partir de archivos de definición YAML
declarativos en lugar de los Dockerfiles tradicionales. Un único archivo YAML describe
exactamente lo que contiene una imagen: paquetes, usuarios, variables de entorno, punto
de entrada (entrypoint) y metadatos. El sistema de construcción de DHI produce una imagen
firmada que contiene únicamente los paquetes necesarios, con una lista de materiales de
software (SBOM) y procedencia de nivel 3 de construcción de SLSA.

Esta página explica cómo escribir un archivo de definición de DHI, construir imágenes
localmente y utilizar patrones avanzados como etapas de construcción (build stages),
repositorios de terceros, rutas de archivos y variantes de desarrollo.

> [!IMPORTANT]
>
> El sistema de construcción de DHI descarga las imágenes base y las herramientas de
> construcción desde `dhi.io`, por lo que debes autenticarte en ese registro antes de
> construir un archivo de definición. Utiliza tus credenciales de Docker ID (el mismo
> usuario y contraseña que utilizas para Docker Hub) al iniciar sesión.
>
> Ejecuta `docker login dhi.io` para autenticarte.

## En qué se diferencian las construcciones de DHI de los Dockerfiles

Un Dockerfile es una secuencia de instrucciones imperativas: `RUN`, `COPY`, `FROM`.
Un archivo de definición de DHI es una especificación declarativa. Describes el
estado deseado de la imagen y el sistema de construcción determina cómo producirlo.

Cada definición de DHI comienza con una directiva de sintaxis que indica a BuildKit
qué frontend de construcción de DHI utilizar. El frontend es el componente que analiza
y procesa las definiciones YAML en lugar del analizador de Dockerfile predeterminado:

```yaml
# syntax=dhi.io/build:2-alpine3.23
```

La versión del frontend corresponde a la distribución base:

| Distribución | Directiva de sintaxis |
|---------------------|----------------------------------------|
| Alpine 3.22         | `# syntax=dhi.io/build:2-alpine3.22`  |
| Alpine 3.23         | `# syntax=dhi.io/build:2-alpine3.23`  |
| Debian 12 (Bookworm)| `# syntax=dhi.io/build:2-debian12`    |
| Debian 13 (Trixie)  | `# syntax=dhi.io/build:2-debian13`    |

El sistema de construcción de DHI lee el archivo YAML, resuelve los paquetes de los
repositorios especificados, ensambla el sistema de archivos, crea cuentas de usuario,
establece los metadatos y produce una imagen OCI firmada.

## Explorar el catálogo como referencia

El [repositorio del catálogo DHI](https://github.com/docker-hardened-images/catalog)
es de código abierto bajo la licencia Apache 2.0 y contiene cada definición de imagen
oficial. Estudiar las definiciones existentes es la mejor manera de aprender los
patrones YAML para los diferentes tipos de imágenes.

El catálogo sigue esta estructura de directorios:

```text
catalog/
├── image/
│   ├── alpine-base/
│   │   ├── alpine-3.23/
│   │   │   ├── 3.23.yaml            # runtime variant
│   │   │   └── 3.23-dev.yaml        # dev variant
│   │   ├── guides.md
│   │   ├── info.yaml
│   │   ├── logo.svg
│   │   └── overview.md
│   ├── nginx/
│   │   ├── alpine-3.22/
│   │   ├── alpine-3.23/
│   │   │   ├── mainline.yaml
│   │   │   ├── mainline-dev.yaml
│   │   │   ├── stable.yaml
│   │   │   └── stable-dev.yaml
│   │   ├── debian-12/
│   │   ├── debian-13/
│   │   ├── bin/
│   │   ├── guides.md
│   │   ├── info.yaml
│   │   ├── logo.svg
│   │   └── overview.md
│   └── redis/
│       ├── debian-13/
│       │   ├── 8.0.yaml              # runtime
│       │   ├── 8.0-dev.yaml          # dev
│       │   ├── 8.0-compat.yaml       # compat runtime
│       │   └── 8.0-compat-dev.yaml   # compat dev
│       ├── guides.md
│       ├── info.yaml
│       ├── logo.svg
│       └── overview.md
├── chart/
└── package/
```

Cada imagen organiza sus variantes por distribución. Las imágenes admiten múltiples
tipos de variantes:

- Una variante de tiempo de ejecución (`runtime`) es mínima y normalmente se ejecuta como
  un usuario sin privilegios (non-root).
- Una variante de desarrollo (`dev`) agrega un shell, un gestor de paquetes y
  herramientas de desarrollo.
- Una variante de compatibilidad agrega utilidades de shell comunes como `bash`,
  `coreutils`, `grep` y `sed` para su uso con flujos de trabajo existentes. Las imágenes
  de compatibilidad utilizan el campo `flavor: compat` junto con una variante `runtime`
  o `dev`.
- Una variante de compatibilidad y desarrollo (`compatibility-dev`) combina los paquetes
  de compatibilidad con herramientas de desarrollo.

Algunas imágenes también admiten variantes adicionales (como las variantes de framework de
software `sfw`). Consulta el catálogo para ver la lista completa de variantes disponibles
para cada imagen.

## Pruébalo: construye una imagen del catálogo

Antes de escribir tu propia definición, prueba a construir una imagen de catálogo
existente directamente desde GitHub:

```console
$ docker buildx build \
    https://raw.githubusercontent.com/docker-hardened-images/catalog/refs/heads/main/image/alpine-base/alpine-3.23/3.23.yaml \
    --sbom=generator=dhi.io/scout-sbom-indexer:1 \
    --provenance=1 \
    --tag my-alpine-base:3.23 \
    --load
```

Esto descarga el archivo de definición directamente desde GitHub y lo construye localmente.
Una vez finalizada la construcción, verifica la imagen:

```console
$ docker images my-alpine-base
```

Para modificar una imagen, clona el catálogo y edita los archivos YAML localmente:

```console
$ git clone https://github.com/docker-hardened-images/catalog.git
$ cd catalog
```

## Referencia del esquema YAML

Las siguientes secciones describen los campos disponibles en un archivo de definición de DHI.

### Campos requeridos

Cada definición debe incluir estos campos de nivel superior:

| Campo | Descripción |
|-------------|---------------------------------------------------------------------|
| `name`      | Nombre legible por humanos para la imagen. |
| `image`     | Ruta completa del registro, como `dhi.io/my-image`. |
| `variant`   | Tipo de variante de la imagen: `runtime` o `dev`. |
| `tags`      | Lista de etiquetas de la imagen. |
| `platforms`  | Arquitecturas de destino, como `linux/amd64` y `linux/arm64`. |
| `contents`  | Repositorios de paquetes y paquetes a instalar. |

### Metadatos de la imagen

Estos campos agregan metadatos a la imagen:

| Campo | Descripción |
|---------------|-------------------------------------------------------------------|
| `os-release`  | Define el contenido del archivo `/etc/os-release` dentro de la imagen. |
| `annotations` | Anotaciones de la imagen OCI como descripción y licencia. |
| `dates`       | Fecha de lanzamiento y fecha de fin de soporte (end-of-life). |
| `vars`        | Variables en tiempo de construcción para plantillas. |
| `flavor`      | Modificador del tipo de imagen (flavor), como `compat` para imágenes de compatibilidad. |

### Configuración del contenedor

Estos campos controlan cómo se ejecuta el contenedor:

| Campo | Descripción |
|---------------|-------------------------------------------------------------------|
| `accounts`    | Usuarios, grupos y el usuario por defecto de ejecución (`run-as`). |
| `environment` | Variables de entorno. |
| `entrypoint`  | Comando del punto de entrada (entrypoint) del contenedor. |
| `cmd`         | Argumentos de comandos predeterminados. |
| `work-dir`    | Directorio de trabajo dentro del contenedor. |
| `volumes`     | Puntos de montaje de volúmenes. |
| `ports`       | Puertos de red expuestos. |
| `paths`       | Directorios, archivos y enlaces simbólicos a crear. |

### Campos avanzados

Estos campos admiten patrones de construcción más complejos:

| Campo | Descripción |
|----------------------|--------------------------------------------------------------|
| `contents.builds`    | Etapas de construcción (build stages) con pipelines de shell. |
| `contents.keyring`   | Claves de firma para repositorios de paquetes de terceros. |
| `contents.artifacts` | Artefactos OCI preconstruidos a incluir. |
| `contents.mappings`  | Asignaciones de Package URL (purl) para la precisión de la SBOM. |
| `contents.files`     | Archivos de origen obtenidos de URLs de Git con sumas de comprobación (checksums). |

## Crear una imagen mínima

Comienza con la definición más sencilla posible: una imagen base de Alpine con un
usuario sin privilegios (non-root).

Crea un directorio para tu proyecto y agrega un archivo llamado `base.yaml`:

```yaml
# syntax=dhi.io/build:2-alpine3.23

name: My Base Image
image: my-registry/my-base
variant: runtime
tags:
  - "1.0.0"
  - "1.0"
platforms:
  - linux/amd64
  - linux/arm64

contents:
  repositories:
    - https://dl-cdn.alpinelinux.org/alpine/v3.23/main
    - https://dl-cdn.alpinelinux.org/alpine/v3.23/community
  packages:
    - alpine-baselayout-data
    - busybox
    - ca-certificates-bundle

accounts:
  run-as: nonroot
  users:
    - name: nonroot
      uid: 65532
      gid: 65532
  groups:
    - name: nonroot
      gid: 65532
      members:
        - nonroot

os-release:
  name: Docker Hardened Images (Alpine)
  id: alpine
  version-id: "3.23"
  pretty-name: My Hardened Image
  home-url: https://docker.com/products/hardened-images/
  bug-report-url: https://docker.com/support/

environment:
  SSL_CERT_FILE: /etc/ssl/certs/ca-certificates.crt

annotations:
  org.opencontainers.image.description: A minimal Alpine base image

cmd:
  - /bin/sh
```

En esta definición:

- `contents.repositories` utiliza URLs completas a espejos (mirrors) de paquetes de Alpine.
- `contents.packages` enumera los nombres exactos de los paquetes de Alpine.
- El bloque `accounts` crea un usuario `nonroot` (UID 65532) y lo establece como el
  usuario predeterminado para el contenedor.
- El bloque `os-release` define lo que aparece en `/etc/os-release`. Incluye siempre
  `bug-report-url` junto con `home-url`.
- El bloque `annotations` añade metadatos OCI visibles en los registros y en los informes de
  Docker Scout.

Construye la imagen:

```console
$ docker buildx build . -f base.yaml \
    --sbom=generator=dhi.io/scout-sbom-indexer:1 \
    --provenance=1 \
    --tag my-base:latest \
    --load
```
> [!NOTE]
>
> El campo `tags` en el archivo de especificación define los metadatos de la imagen (etiquetas
> de variante y versión integradas en el manifiesto de la imagen). La opción `--tag` en la CLI
> establece la referencia de la imagen OCI utilizada para subir (push) o cargar (load) la imagen.
> Estos tienen propósitos diferentes: las etiquetas del archivo de especificación describen
> *qué es la imagen*, mientras que la etiqueta de la CLI determina *dónde se almacena*.

## Utilizar una base Debian con repositorios de terceros

Para aplicaciones que requieren paquetes de Debian o repositorios APT de terceros,
utiliza la directiva de sintaxis de Debian. El siguiente ejemplo construye una imagen de
Redis a partir del repositorio APT oficial de Redis.

Crea un archivo llamado `redis.yaml`:

```yaml
# syntax=dhi.io/build:2-debian13

name: Redis 8.0.x
image: my-registry/my-redis
variant: runtime
tags:
  - "8.0"
  - "8.0.5"
platforms:
  - linux/amd64
  - linux/arm64

contents:
  repositories:
    - deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb trixie main
  keyring:
    - https://packages.redis.io/gpg
  packages:
    - '!libelogind0'
    - '!mawk'
    - '!original-awk'
    - base-files
    - libpcre2-8-0
    - libssl3t64
    - libstdc++6
    - libsystemd0
    - redis=6:8.0.5-1rl1~trixie1
    - redis-server=6:8.0.5-1rl1~trixie1
    - redis-tools=6:8.0.5-1rl1~trixie1
    - tini
  mappings:
    redis: pkg:deb/redis/redis@6:8.0.5-1rl1~trixie1?os_name=debian&os_version=13
    redis-server: pkg:deb/redis/redis-server@6:8.0.5-1rl1~trixie1?os_name=debian&os_version=13
    redis-tools: pkg:deb/redis/redis-tools@6:8.0.5-1rl1~trixie1?os_name=debian&os_version=13

accounts:
  run-as: nonroot
  users:
    - name: nonroot
      uid: 65532
      gid: 65532
  groups:
    - name: nonroot
      gid: 65532
      members:
        - nonroot

os-release:
  name: Docker Hardened Images (Debian)
  id: debian
  version-id: "13"
  version-codename: trixie
  pretty-name: Docker Hardened Images/Debian GNU/Linux 13 (trixie)
  home-url: https://docker.com/products/hardened-images/
  bug-report-url: https://docker.com/support/

work-dir: /data

environment:
  REDIS_VERSION: 8.0.5

annotations:
  org.opencontainers.image.description: A minimal Redis image
  org.opencontainers.image.licenses: AGPL-3.0-only

entrypoint:
  - /usr/bin/tini
  - --

cmd:
  - redis-server
  - /etc/redis/redis.conf
  - --include
  - /etc/redis/conf.d/*.conf
```

Este ejemplo introduce varios patrones:

- **Repositorios de terceros**: El campo `repositories` utiliza el formato `deb
  [signed-by=...] URL suite component` de Debian para las fuentes de APT.
- **Llavero de claves (Keyring)**: El campo `keyring` descarga la clave GPG
  utilizada para verificar los paquetes del repositorio de terceros.
- **Exclusiones de paquetes**: Antepón un signo `!` al nombre de un paquete para
  excluirlo explícitamente. Esto evita que se instalen dependencias no deseadas. En este
  caso, se excluyen `!libelogind0`, `!mawk` y `!original-awk`.
- **Fijación de versión de Debian**: Utiliza el formato completo con epoch,
  `redis-server=6:8.0.5-1rl1~trixie1`, para fijar las versiones exactas de los paquetes.
- **Asignaciones de SBOM**: El campo `mappings` proporciona metadatos de Package
  URL (purl) para que Docker Scout pueda identificar con precisión el software en la SBOM.
- **Proceso de inicio**: El punto de entrada (`entrypoint`) utiliza `tini` como
  un proceso de inicio ligero (PID 1) para gestionar el reenvío de señales y la
  eliminación de procesos zombi.
- **Inclusión de configuración**: El comando (`cmd`) utiliza `--include
  /etc/redis/conf.d/*.conf` para que los archivos de configuración creados en la
  sección `paths` se carguen al inicio.

## Crear rutas

Utiliza el campo `paths` para crear directorios, archivos con contenido en línea y
enlaces simbólicos dentro de la imagen. El siguiente ejemplo amplía la definición de Redis
con las rutas requeridas para su funcionamiento:

```yaml
paths:
  - type: directory
    path: /var/lib/redis
    uid: 65532
    gid: 65532
    mode: "0755"
  - type: directory
    path: /var/log/redis
    uid: 65532
    gid: 65532
    mode: "0755"
  - type: directory
    path: /run/redis/
    uid: 65532
    gid: 65532
    mode: "0755"
  - type: directory
    path: /data
    uid: 65532
    gid: 65532
    mode: "0755"
  - type: file
    path: /etc/redis/conf.d/docker.conf
    content: |
      daemonize no
      bind 0.0.0.0 -::1
      logfile ""
    uid: 0
    gid: 0
    mode: "0555"
  - type: symlink
    path: /usr/bin/redis-sentinel
    uid: 0
    gid: 0
    source: /usr/bin/redis-check-rdb
```

Hay tres tipos de rutas disponibles:

| Tipo | Campos requeridos | Descripción |
|-------------|----------------------------------|--------------------------------------|
| `directory` | `path`, `uid`, `gid`, `mode`     | Crea un directorio vacío. |
| `file`      | `path`, `content`, `uid`, `gid`, `mode` | Crea un archivo con contenido en línea. |
| `symlink`   | `path`, `source`, `uid`, `gid`   | Crea un enlace simbólico. |

El campo `mode` utiliza una representación en cadena de los bits de permisos octales,
como `"0755"` para lectura-escritura-ejecución por parte del propietario o `"0555"` para
lectura-ejecución por parte de todos. Ten en cuenta que el tipo `file` admite `content` en
línea utilizando una cadena multilínea de YAML.

## Agregar etapas de construcción

Para las imágenes que necesitan ejecutar comandos de shell durante la construcción,
como la configuración de archivos, la creación de enlaces simbólicos o el ajuste de
permisos, utiliza el campo `contents.builds`. Cada etapa de construcción (build stage)
tiene sus propios paquetes, un pipeline de pasos nombrados y asignaciones de salida.

El siguiente ejemplo configura Nginx durante la construcción para que se ejecute en un
puerto sin privilegios y deshabilite los tokens de servidor:

```yaml
# syntax=dhi.io/build:2-alpine3.23

name: Nginx mainline
image: my-registry/my-nginx
variant: runtime
tags:
  - "1.29"
platforms:
  - linux/amd64
  - linux/arm64

contents:
  repositories:
    - https://dl-cdn.alpinelinux.org/alpine/v3.23/main
    - https://dl-cdn.alpinelinux.org/alpine/v3.23/community
    - http://nginx.org/packages/mainline/alpine/v3.23/main
  keyring:
    - https://nginx.org/keys/nginx_signing.rsa.pub
  packages:
    - alpine-baselayout-data
    - busybox
    - musl-utils
    - nginx=1.29.5-r1
  builds:
    - name: nginx
      contents:
        repositories:
          - https://dl-cdn.alpinelinux.org/alpine/v3.23/main
          - https://dl-cdn.alpinelinux.org/alpine/v3.23/community
          - http://nginx.org/packages/mainline/alpine/v3.23/main
        keyring:
          - https://nginx.org/keys/nginx_signing.rsa.pub
        packages:
          - alpine-baselayout-data
          - bash
          - musl-utils
          - nginx=1.29.5-r1
      pipeline:
        - name: install
          runs: |
            set -eux -o pipefail

            ln -sf /dev/stdout /var/log/nginx/access.log
            ln -sf /dev/stderr /var/log/nginx/error.log

            sed -i "s,listen       80;,listen       8080;," /etc/nginx/conf.d/default.conf
            sed -i "/user  nginx;/d" /etc/nginx/nginx.conf
            sed -i "s,pid        /run/nginx.pid;,pid        /var/run/nginx.pid;," /etc/nginx/nginx.conf
            sed -i '/^http {$/a\    server_tokens off;' /etc/nginx/nginx.conf

            chown -R 65532:65532 /var/cache/nginx
            chmod -R g+w /var/cache/nginx
            chown -R 65532:65532 /etc/nginx
            chmod -R g+w /etc/nginx
            chown -R 65532:65532 /run
            chown -R 65532:65532 /run/lock
            chown -R 65532:65532 /var/run
            chown -R 65532:65532 /var/log/nginx
      outputs:
        - source: /
          target: /
          uid: 0
          gid: 0
          diff: true

accounts:
  run-as: nginx
  users:
    - name: nginx
      uid: 65532
      gid: 65532
  groups:
    - name: nginx
      gid: 65532
      members:
        - nginx
    - name: www-data
      gid: 82

os-release:
  name: Docker Hardened Images (Alpine)
  id: alpine
  version-id: "3.23"
  pretty-name: Docker Hardened Images/Alpine Linux v3.23
  home-url: https://docker.com/products/hardened-images/
  bug-report-url: https://docker.com/support/

environment:
  NGINX_VERSION: 1.29.5-r1

annotations:
  org.opencontainers.image.description: A minimal Nginx image
  org.opencontainers.image.licenses: BSD-2-Clause

entrypoint:
  - nginx

cmd:
  - -g
  - daemon off;

ports:
  - 8080/tcp
```

Patrones clave en esta definición:

| Elemento | Descripción |
|-------------|----------------------------------------------------------------------------|
| `contents`  | Cada etapa de construcción tiene su propia sección `contents`. Incluye paquetes necesarios únicamente durante la construcción, como `bash`. |
| `pipeline`  | Contiene pasos nombrados que ejecutan comandos de shell. Comienza siempre los scripts con `set -eux -o pipefail`. |
| `outputs`   | Copia los resultados de la etapa de construcción a la imagen final. Al establecer `diff: true`, se copian únicamente los archivos que cambiaron, manteniendo la imagen mínima. |
| `accounts`  | Nginx utiliza un usuario `nginx` dedicado (UID 65532) en lugar de `nonroot`. El grupo `www-data` (GID 82) también se crea para la compatibilidad con el servidor web. |
| `musl-utils` | Requerido tanto en los paquetes principales como en los de construcción para las imágenes de Nginx basadas en Alpine. |

## Utilizar artefactos OCI como fuentes de paquetes

En lugar de instalar paquetes desde los repositorios de Alpine o Debian, puedes descargar
binarios preconstruidos a partir de los artefactos de paquetes de DHI. Así es como el
catálogo construye imágenes como Python y Node.js: el entorno de ejecución se compila
por separado y se publica como un artefacto OCI, y luego se referencia por su digest
en la definición de la imagen.

Agrega el campo `artifacts` bajo `contents`:

```yaml
contents:
  repositories:
    - https://dl-cdn.alpinelinux.org/alpine/v3.23/main
    - https://dl-cdn.alpinelinux.org/alpine/v3.23/community
  packages:
    - alpine-baselayout-data
    - bzip2
    - ca-certificates-bundle
    - expat
    - gdbm
    - libffi
    - mpdecimal
    - musl
    - ncurses
    - openssl
    - readline
    - sqlite-libs
    - tzdata
    - zlib
  artifacts:
    - name: dhi.io/pkg-python:3.13.12-alpine3.23@sha256:052b3b915055006a27c42470eed5c65d7ee92d2c3de47ecaedcc6bbd36077b95
      includes:
        - opt/**
      uid: 0
      gid: 0
```

| Campo | Descripción |
|------------|------------------------------------------------------------------------------|
| `name`     | Referencia OCI completa con fijación de digest. Utiliza siempre `@sha256:` para garantizar la reproducibilidad. |
| `includes` | Patrones de coincidencia (glob) para los archivos a extraer del artefacto. Las rutas se resuelven desde la raíz del sistema de archivos; `opt/**` incluye todo lo que se encuentra bajo la ruta `/opt`. |
| `excludes` | Patrones de coincidencia (glob) para los archivos a omitir. Útil para eliminar cabeceras, documentación o binarios no utilizados. |
| `uid`, `gid` | Propietario y grupo para los archivos extraídos. |

Los paquetes DHI disponibles se encuentran en el directorio
[`package/`](https://github.com/docker-hardened-images/catalog/tree/main/package)
del repositorio del catálogo.

## Crear una variante de desarrollo

Una variante de desarrollo (`dev`) de una imagen añade un shell, un gestor de paquetes y
herramientas de desarrollo. Esto es útil para la depuración y para su uso como una
etapa de construcción en flujos de trabajo de varias etapas.

Para crear una variante de desarrollo, cambia el campo `variant` y habilita el acceso root:

```yaml
# syntax=dhi.io/build:2-alpine3.23

name: Alpine 3.23 Base (dev)
image: my-registry/my-base
variant: dev
tags:
  - "1.0-dev"
platforms:
  - linux/amd64
  - linux/arm64

contents:
  repositories:
    - https://dl-cdn.alpinelinux.org/alpine/v3.23/main
    - https://dl-cdn.alpinelinux.org/alpine/v3.23/community
  packages:
    - alpine-baselayout-data
    - apk-tools
    - busybox
    - ca-certificates-bundle

accounts:
  root: true
  run-as: root
  users:
    - name: nonroot
      uid: 65532
      gid: 65532
  groups:
    - name: nonroot
      gid: 65532
      members:
        - nonroot

os-release:
  name: Docker Hardened Images (Alpine)
  id: alpine
  version-id: "3.23"
  pretty-name: Docker Hardened Images/Alpine Linux v3.23
  home-url: https://docker.com/products/hardened-images/
  bug-report-url: https://docker.com/support/

environment:
  SSL_CERT_FILE: /etc/ssl/certs/ca-certificates.crt

annotations:
  org.opencontainers.image.description: A minimal Alpine base image

cmd:
  - /bin/sh
```

Las diferencias clave con respecto a una variante de ejecución (runtime):

- `variant: dev` en lugar de `variant: runtime`.
- `accounts.root: true` habilita la cuenta root.
- `run-as: root` establece root como el usuario predeterminado.
- Se añade `apk-tools` a los paquetes, dotando a la imagen de un gestor de paquetes.
- El usuario `nonroot` se sigue definiendo para que las aplicaciones puedan cambiar a un
  usuario sin privilegios en tiempo de ejecución.

Para las variantes de desarrollo basadas en Debian, añade `apt` en lugar de `apk-tools`
e incluye la variable de entorno `DEBIAN_FRONTEND: noninteractive`.

## Crear una variante de compatibilidad

Una variante de compatibilidad incluye utilidades comunes de shell para su uso con
scripts y herramientas de automatización que esperan un entorno de usuario estándar de
Linux. Las imágenes de compatibilidad utilizan el campo `flavor`:

```yaml
variant: runtime
flavor: compat
```

Una variante de compatibilidad añade paquetes como `bash`, `coreutils`, `findutils`,
`grep`, `hostname`, `openssl`, `procps` y `sed` junto a los paquetes de la aplicación.
Una variante de compatibilidad y desarrollo (`compatibility-dev`) combina tanto los
paquetes de compatibilidad como las herramientas de desarrollo:

```yaml
variant: dev
flavor: compat
```

Consulta las imágenes de compatibilidad de Redis en el catálogo para ver un ejemplo
completo del patrón de compatibilidad.

## Configurar puertos y volúmenes

Utiliza el campo `ports` para declarar qué puertos expone el contenedor. Utiliza siempre
puertos sin privilegios (superiores al 1024) cuando el contenedor se ejecute como un
usuario no root.

```yaml
ports:
  - 8080/tcp
```

Utiliza el campo `volumes` para declarar los puntos de montaje de volúmenes:

```yaml
volumes:
  - /data
```

## Configurar anotaciones

Las anotaciones OCI añaden metadatos legibles por máquina a la imagen. Utiliza el campo
`annotations`:

```yaml
annotations:
  org.opencontainers.image.description: A minimal hardened application image
  org.opencontainers.image.licenses: Apache-2.0
```

Estas anotaciones aparecen en los informes de Docker Scout y en las interfaces del registro
de contenedores.

## Construir y verificar

### Construir la imagen

Construye una imagen de una sola plataforma para pruebas locales:

```console
$ docker buildx build . -f my-image.yaml \
    --sbom=generator=dhi.io/scout-sbom-indexer:1 \
    --provenance=1 \
    --tag my-image:latest \
    --load
```

### Inspeccionar la SBOM

Visualiza la lista de materiales de software (SBOM) generada:

```console
$ docker scout sbom my-image:latest
```

### Escanear en busca de vulnerabilidades

Comprueba la imagen contra las bases de datos de CVE conocidas:

```console
$ docker scout cves my-image:latest
```

### Comparar con una imagen no endurecida

Mide la mejora de la seguridad frente a una imagen equivalente no endurecida:

```console
$ docker scout compare my-image:latest \
    --to <non-hardened-equivalent>:<tag> \
    --platform linux/amd64
```

Reemplaza `<non-hardened-equivalent>` con la Docker Official Image o la imagen de la
comunidad con la que te estás comparando.

### Inspeccionar con Docker Debug

Verifica la configuración de `os-release` y `entrypoint`:

```console
$ docker debug my-image:latest
```

La salida muestra el nombre de la distribución detectada a partir de tu configuración de
`os-release` y ejecuta una comprobación de linter del punto de entrada.

## Subir a un registro

Etiqueta y sube la imagen a tu registro de contenedores:

```console
$ docker tag my-image:latest <your-namespace>/my-image:latest
```

```console
$ docker push <your-namespace>/my-image:latest
```

Reemplaza `<your-namespace>` con tu nombre de usuario de Docker Hub o el espacio de nombres
de tu organización.

## Contribuir al catálogo

Docker Hardened Images es un proyecto de código abierto. Puedes contribuir con nuevas
definiciones de imágenes o mejorar las existentes enviando una solicitud de extracción (pull
request) al repositorio del catálogo.

Para contribuir con una nueva imagen:

1. Haz un fork del repositorio del catálogo.
2. Crea un directorio bajo `image/` siguiendo la convención de nomenclatura:
   `image/<image-name>/<distribution>/`.
3. Añade tus archivos de definición YAML (uno por variante).
4. Añade un `info.yaml` con el nombre para mostrar, la descripción y las categorías.
5. Añade un `overview.md` que describa la imagen.
6. Añade un `logo.svg` para el icono de la imagen.
7. Añade un `guides.md` con la documentación de uso.
8. Abre una solicitud de extracción (pull request) contra la rama `main`.

Para obtener más detalles, lee la [guía de contribución](https://github.com/docker-hardened-images/catalog/blob/main/CONTRIBUTING.md)
en el repositorio del catálogo.

