Empezando con Docker - Imágenes y contenedores

Publicado el Lunes, 12 de enero de 2026

Docker es un servicio de contenedores de software que se ha vuelto bastante popular a la hora de publicar aplicaciones en producción. Básicamente, permite empaquetar software de una forma que es conceptualmente similar a una máquina virtual. A diferencia de una máquina virtual, los contenedores no cuentan con software de plataforma, por lo que son bastante livianos y portables. El software empaquetado se puede ejecutar igual en cualquier plataforma que ejecute Docker.

La tecnología introduce algunos términos nuevos, que resultan un poco confusos al empezar a usar Docker. ¿Qué es una Imagen Docker? ¿Qué es un contenedor Docker? ¿Cómo lo ejecuto? Este post intenta explicar todos éstos términos de la forma más directa y con un ejemplo bien básico.

El primer paso es descargar e instalar Docker. Se puede obtener para varias distribuciones GNU/Linux (CentOS, Debian, Fedora, Ubuntu, y más), Mac OS y Windows.

Una vez instalado Docker, necesitamos crear un archivo Dockerfile. El Dockerfile es un set de instrucciones para que Docker construya una imagen. Se puede pensar como el código fuente o un plano. Por defecto, Docker va a buscar un archivo de nombre "Dockerfile" sin extensión. Se puede usar el parámetro -f para especificar un archivo distinto, pero es buena práctica usar el nombre por defecto y tener un Dockerfile por directorio si necesitas más de una imagen en un proyecto.

Empecemos por escribir nuestro Dockerfile:

FROM ruby:alpine
WORKDIR /picandocodigo

Lo que estamos diciendo con éstas dos líneas es "quiero basar mi imagen en ruby:alpine", esto va a construir sobre dicha imagen. Podemos explorar Docker Hub, un repositorio de imágenes Docker, para encontrar más imágenes en las que basarnos. Estamos usando ruby:alpine, porque vamos a ejecutar código Ruby. Esta imagen nos provee una distribución Linux liviana con Ruby instalado. La imagen alpine es un punto de partida popular debido a ser muy pequeña (~5MB) y minimalista.

También estamos diciendo "usa /picandocodigo como nuestro directorio de trabajo". Luego iremos agregando algunas cosas al Dockerfile para que haga algo además de descargar la imagen y definir el directorio de trabajo:

FROM ruby:alpine
WORKDIR /picandocodigo

EXPOSE 4000

RUN apk add --no-cache bash wget
RUN wget https://picandocodigo.net/img/diver.gif -O diver.gif
RUN gem install webrick
CMD ruby -run -e httpd . -p 4000

Le estamos pidiendo a Docker que exponga el puerto 4000 e instale bash y wget con "apk add", porque así de minimalista es Alpine Linux. Ejecutamos wget para descargar un archivo gif y lo guardamos a 'tiburon.gif'. Después instalamos la gema webrick para servir contenido por HTTP. Por último, usamos CMD, una instrucción que define el comando a ser ejecutado cuando corramos un contenedor a partir de nuestra imagen. En este caso ejecutamos un comando Ruby que sirve el directorio actual ('/picandocodigo', el que definimos en WORKDIR) a la web en el puerto 4000.

Es un buen momento para hablar sobre imágenes Docker ("Docker images") y contenedores Docker ("Docker containers"). Una vez que tenemos escrito nuestro Dockerfile, generamos una Imagen Docker con él. Algo que me ayudó a entender qué es una imagen, fue compararlo con una imagen ISO (de cuando quemas un CD o pendrive USB). No es exactamente lo mismo, pero fue la primera cosa que me vino a la mente al pensar en una "imagen". Si vienen del paradigma de Programación Orientada a Objetos, una imagen sería una "clase".

Con una imagen, podemos ejecutar contenedores Docker. Éstos serían los "objetos", si seguimos el paradigma de Programación Orientada a Objetos: instanciamos contenedores (objetos) a partir de una imagen (clase). Podemos ejecutar cuantos contenedores Docker queramos con la misma imagen. También podemos construir una imagen sobre otra imagen, como estamos haciendo en el ejemplo sobre la imagen ruby:alpine.

Ahora que tenemos nuestro Dockerfile armado, podemos construirlo:

$ docker build -t picandocodigo/diver .

Necesitamos decirle a "docker build" dónde está el archivo Dockerfile, así que estamos pasando . como parámetro para decir que está en el directorio actual. El parámetro -t nos permite darle un nombre y opcionalmente una etiqueta (con el formato name:etiqueta) a nuestra imagen, la cual ha sido construida ahora:

$ docker images
IMAGE                        ID             DISK USAGE   CONTENT SIZE
picandocodigo/diver:latest   df94147085f5        158MB         52.2MB

Así que ahora podemos ejecutar un contenedor con esta imagen usando su etiqueta:

$ docker run -i -t -p 4242:4000 picandocodigo/diver

Para procesos interactivos (como un shell), usamos el parámetro -i para mantener STDIN abierto incluso si no está atado, y -t para alocar una pseudo-TTY al proceso del contenedor. Podemos escribirlo también como -it. Le estamos diciendo a Docker que mapee el puerto 4242 de nuestro sistema al puerto 4000 del contenedor (con -p).

Si visitamos http://localhost:4242/diver.gif en nuestro navegador, vamos a ver una animación submrina. Podemos usar cualquier puerto, como el puerto 80 o 3000, pero hay que fijarse que éstos puertos pueden estar en uso (en particular el puerto 80 es el puerto por defecto para servidores web). Ya que éstos son contenedores, basados en nuestra imagen, podemos correr otra instancia, por ejemplo en el puerto 9090:

docker run -i -t -p 9090:4000 picandocodigo/diver

La misma imagen va a estar disponible en http://localhost:4242/diver.gif.

Hice este diagrama que espero ayude a ilustrar la idea y sea más fácil interpretar los conceptos:

Diagrama - Dockerfile, imágenes y contenedores

Podemos encontrar más información sobre Docker en su Guía Inicial o visitar su documentación oficial. Algunos comandos importantes más que nos pueden servir de referencia:

  • docker image rm - Nos permite eliminar una imagen. Le podemos pasar el ID o la etiqueta de la imagen. Podemos ver las imágenes con docker images como hicimos más arriba.
  • docker container rm - A veces queremos borrar una imagen, y no podemos porque hay un contenedor que la está usando. O simplemente queremos eliminar un contenedor. Este es el comando para eso.
  • docker exec - Ejecuta un comando en un contenedor en ejecución. Por ejemplo en mi post sobre Forgejo, ejecuté docker exec -it forgejo /bin/sh para tener una terminal sh dentro del contenedor.

La documentación es bastante buena y todos estos comandos y más están documentados ahí. Espero que haya servidor

Este post fue originalmente publicado en inglés en noviembre de 2017 en el blog de Cultivate (mi antiguo trabajo). Lo traduje al español, actualicé, revisé y agregué un poco de contenido extra. En su momento tuvo muy buen recibimiento, espero que todavía sea de ayuda. Cuando todavía existía Twitter, ¡Docker compartió el post en su cuenta oficial! Muy buen feedback 🙂
Docker en Twitter mencionando a @picandocodigo

He visto hablar bastante sobre Podman recientemente. No me he metido en el tema todavía como para saber bien cómo funciona, pero tengo entendido que tiene una API compatible con Docker. Así que debería ser relativamente fácil aprenderlo viniendo de Docker. Pero eso es un tema para otro día... (pero qué simpáticas las mascotas).

No hay comentarios en este post

Feed de comentarios

Dejar un comentario

Toasty!