Docker es un sistema de contenedores de aplicación: empaqueta un proceso junto con todo lo que necesita para ejecutarse (binarios, bibliotecas, configuración) en una imagen inmutable, y luego ejecuta esa imagen en un entorno aislado llamado contenedor. El aislamiento usa primitivas del kernel de Linux —específicamente namespaces y cgroups— no virtualización completa, por lo que el proceso comparte el mismo kernel que el host pero no puede ver el resto del sistema de ficheros ni los procesos de otros contenedores.
La razón por la que Docker tiene sentido es la reproducibilidad: la misma imagen se comporta igual en tu portátil, en el servidor de staging y en producción. El “en mi máquina funciona” desaparece porque la máquina viaja con el código.
¿Cuándo usarlo? Cuando necesitas aislar dependencias entre proyectos, cuando despliegas servicios que tienen configuraciones incompatibles entre sí, cuando quieres que un equipo arranque un entorno de desarrollo con un único comando, o cuando empaquetas software para que corra en cualquier Linux sin instalación manual. Lo que se rompe si lo haces mal es más sutil de lo que parece: montar volúmenes con permisos incorrectos corrompe datos o deja el contenedor sin acceso a su almacenamiento persistente, exponer puertos sin firewall los hace accesibles desde cualquier IP, y usar la imagen latest sin fijar versión convierte las actualizaciones en ruleta rusa.
Antes de tocar nada, el punto crítico en Debian es de dónde instalas Docker. Los repositorios oficiales de Debian incluyen un paquete llamado docker.io que es funcional pero va varias versiones por detrás del upstream. Para producción o para seguir la documentación oficial sin fricciones, usas el repositorio de Docker Inc.
[Ubuntu]: En Ubuntu ocurre lo mismo pero con el añadido de que existe también un snap de Docker; evítalo en servidores, tiene overhead y comportamientos de red que sorprenden.
# 1. Dependencias para añadir repositorios HTTPS
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg
# 2. Clave GPG oficial de Docker
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg \
| sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
# 3. Repositorio estable para Debian Bookworm (amd64)
echo \
"deb [arch=$(dpkg --print-architecture) \
signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/debian \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
| sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io \
docker-buildx-plugin docker-compose-plugin
# 4. Añadir tu usuario al grupo docker para no usar sudo cada vez
# (requiere cerrar sesión y volver a entrar para que tome efecto)
sudo usermod -aG docker "$USER"
# --- A partir de aquí se asume que el grupo ya está activo ---
# Levantar nginx en background, mapeando el puerto 80 del host al 80 del contenedor
docker run -d -p 80:80 --name web nginx:1.27
# Ver contenedores en ejecución
docker ps
# Ver todos los contenedores, incluyendo los detenidos
docker ps -a
# Seguir los logs en tiempo real (Ctrl+C para salir)
docker logs -f web
# Shell interactiva dentro del contenedor en ejecución
docker exec -it web bash
# Parar y eliminar el contenedor (en ese orden)
docker stop web
docker rm web
# --- Persistencia con volumen nombrado ---
# Docker gestiona el almacenamiento; más portable que rutas absolutas del host
docker volume create web_data
docker run -d \
-p 80:80 \
--name web \
-v web_data:/usr/share/nginx/html \
nginx:1.27
# --- Construir una imagen propia ---
mkdir -p ~/proyecto-web && cd ~/proyecto-web
cat > index.html <<'EOF'
<h1>Hola desde el contenedor</h1>
EOF
# El Dockerfile es la receta: cada instrucción genera una capa de la imagen
cat > Dockerfile <<'EOF'
# Imagen base oficial de nginx en versión fija, no "latest"
FROM nginx:1.27-alpine
# Copiamos nuestro contenido al directorio que nginx sirve por defecto
COPY index.html /usr/share/nginx/html/index.html
# Documentamos el puerto que el proceso escucha (no lo abre, solo lo declara)
EXPOSE 80
# CMD hereda el de la imagen base; lo dejamos para no sobreescribir el comportamiento de nginx
EOF
# Construir la imagen con nombre y tag explícitos
# El punto final indica que el contexto de build es el directorio actual
docker build -t proyecto-web:1.0 .
# Verificar que la imagen existe localmente
docker image ls proyecto-web
# Ejecutar nuestra imagen
docker run -d -p 8080:80 --name mi-web proyecto-web:1.0
# --- Stack multi-contenedor con Docker Compose ---
cd ~/proyecto-web
cat > docker-compose.yml <<'EOF'
services:
web:
image: proyecto-web:1.0
ports:
- "8080:80"
restart: unless-stopped # systemd-like: reinicia salvo que lo pares explícitamente
db:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: secreto
POSTGRES_DB: appdb
volumes:
- pgdata:/var/lib/postgresql/data
restart: unless-stopped
volumes:
pgdata: # volumen nombrado gestionado por Docker
EOF
# Levantar el stack en background; crea la red y los volúmenes que no existan
docker compose up -d
# Ver estado de todos los servicios del stack
docker compose ps
# Ver logs del servicio db específicamente
docker compose logs db
# Parar el stack sin eliminar datos (los volúmenes se conservan)
docker compose down
# Para eliminar también los volúmenes (¡borra los datos!):
# docker compose down -v
Qué está pasando en cada decisión
El repositorio y los cuatro paquetes: docker-ce es el daemon; docker-ce-cli es el cliente que ejecutas en la terminal; containerd.io es el runtime de bajo nivel que docker-ce delega para gestionar los ciclos de vida de los contenedores; docker-compose-plugin instala Compose como subcomando (docker compose) en lugar del binario separado docker-compose v1, que está en desuso.
El grupo docker: añadir tu usuario al grupo evita usar sudo en cada comando, pero ten claro lo que implica —cualquier usuario en ese grupo puede montar / del host dentro de un contenedor y escalar privilegios. En servidores multiusuario, evalúa si realmente quieres esto o si prefieres Podman.
-d y -p 80:80: -d desacopla el contenedor de tu terminal, que es el comportamiento esperable para servicios. La sintaxis de -p es siempre host:contenedor; si los inviertes, el puerto que se expone en tu máquina no es el que escucha el proceso.
nginx:1.27 en lugar de nginx:latest: fijar la versión en el run y en el FROM del Dockerfile hace que tus builds sean reproducibles. latest significa “lo que sea que haya en el registro ahora mismo”, que puede cambiar entre builds.
Volúmenes nombrados vs bind mounts: el bind mount -v /ruta/host:/ruta/contenedor es directo y fácil de inspeccionar, pero acopla el contenedor a una ruta absoluta del host. El volumen nombrado web_data:/usr/share/nginx/html le cede a Docker la gestión del almacenamiento (/var/lib/docker/volumes/web_data/), lo que facilita backups con docker volume inspect y la portabilidad entre máquinas con docker compose.
Las capas del Dockerfile: cada instrucción FROM, COPY, RUN genera una capa inmutable. Docker las cachea; si cambias solo index.html, al reconstruir solo re-ejecuta las capas posteriores a COPY. Por eso se pone COPY al final siempre que sea posible y se agrupan los RUN en una sola instrucción encadenada con && para no generar capas intermedias con basura.
EXPOSE es documentación, no apertura de puertos: solo le dice a quien lea el Dockerfile y a herramientas como docker compose qué puerto usa el proceso. El puerto no se abre al host hasta que usas -p en docker run o la sección ports: en Compose.
restart: unless-stopped en Compose: equivale a decirle a systemd Restart=on-failure con la excepción de que si el operador para el servicio explícitamente con docker compose down, no vuelve a arrancar solo. Es el valor sensato para servicios de producción.
Podman como alternativa: si trabajas en un entorno donde correr un daemon como root es inaceptable —sistemas con equipos de seguridad estrictos, máquinas compartidas— Podman es compatible con la CLI de Docker (alias docker=podman funciona en la mayoría de casos), no tiene daemon, y los contenedores los ejecuta tu propio usuario. En Debian está en los repositorios estándar: apt install podman. La limitación real es que docker compose no funciona directamente; necesitas podman-compose o generar unidades systemd con podman generate systemd.
N° 104