Docker en Ubuntu: Guía Completa de Instalación y Uso
Docker es una plataforma de contenedorización de código abierto que empaqueta aplicaciones y sus dependencias en unidades aisladas y portátiles llamadas contenedores. A diferencia de las máquinas virtuales, los contenedores comparten el kernel del sistema operativo del host, lo que los hace significativamente más ligeros, más rápidos de iniciar y más eficientes en el uso de recursos — una distinción crítica para cualquiera que ejecute cargas de trabajo en un entorno de VPS Hosting donde los recursos de cómputo afectan directamente el costo y el rendimiento.
Esta guía cubre el proceso completo de instalación de Docker en Ubuntu 20.04, 22.04 y 24.04 LTS, incluyendo el fortalecimiento posterior a la instalación, flujos de trabajo de Docker Compose y patrones de comandos relevantes para producción que la mayoría de los tutoriales omiten.
Requisitos previos y requisitos del sistema
Antes de ejecutar un solo comando, verifique lo siguiente:
- Versión de Ubuntu: 20.04 LTS (Focal), 22.04 LTS (Jammy) o 24.04 LTS (Noble). El comando `lsb_release -cs` utilizado durante la configuración del repositorio detectará automáticamente su nombre en clave.
- Arquitectura: `amd64`, `arm64` o `armhf` son compatibles con el repositorio oficial de Docker.
- Versión del kernel: Docker requiere el kernel de Linux 3.10 o superior. Ejecute `uname -r` para confirmarlo.
- Privilegios de usuario: `sudo` o acceso root es obligatorio para la instalación y la gestión del daemon.
- Espacio en disco: Como mínimo 2 GB libres en la partición que aloja `/var/lib/docker`, que es donde Docker almacena imágenes, contenedores, volúmenes y caché de compilación. En sistemas de producción, monte este directorio en una partición o volumen dedicado.
Paso crítico previo a la instalación: Si anteriormente instaló Docker desde el repositorio `apt` predeterminado de Ubuntu (el paquete `docker.io`), elimínelo primero para evitar conflictos con los paquetes oficiales de Docker CE:
“`bash
sudo apt remove docker docker-engine docker.io containerd runc
“`
Paso 1: Actualizar los paquetes del sistema
Actualice el índice de paquetes y los paquetes instalados a sus últimas versiones antes de agregar cualquier repositorio nuevo:
“`bash
sudo apt update
sudo apt upgrade -y
“`
Esto garantiza que el resolvedor de dependencias de `apt` trabaje con los metadatos de paquetes actuales y que las bibliotecas base del sistema no estén desactualizadas — una fuente común de errores sutiles en tiempo de ejecución con aplicaciones en contenedores.
Paso 2: Instalar Docker Engine desde el repositorio oficial
Los repositorios `apt` predeterminados de Ubuntu incluyen un paquete llamado `docker.io`, que es mantenido por Canonical y generalmente va varias versiones por detrás de la versión oficial de Docker. Para uso en producción, instale siempre desde el repositorio propio de Docker.
2.1 Instalar dependencias de transporte y verificación
“`bash
sudo apt install apt-transport-https ca-certificates curl software-properties-common gnupg lsb-release -y
“`
¿Por qué `gnupg`? A partir de Ubuntu 22.04, `gpg` no siempre está presente de forma predeterminada. Incluirlo explícitamente evita que la importación de la clave GPG falle silenciosamente.
2.2 Agregar la clave GPG oficial de Docker
“`bash
sudo install -m 0755 -d /usr/share/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg –dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
sudo chmod a+r /usr/share/keyrings/docker-archive-keyring.gpg
“`
El paso `chmod a+r` se omite con frecuencia en los tutoriales, pero es necesario en sistemas donde `apt` se ejecuta bajo un contexto de usuario restringido — sin él, el gestor de paquetes no puede leer el llavero y generará un error `NO_PUBKEY` durante `apt update`.
2.3 Agregar el repositorio estable de Docker
“`bash
echo
"deb [arch=$(dpkg –print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg]
https://download.docker.com/linux/ubuntu
$(lsb_release -cs) stable" |
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
“`
La sustitución `arch=$(dpkg –print-architecture)` es esencial en servidores basados en ARM. Codificar `amd64` aquí es un error común que provoca fallos silenciosos en la resolución de paquetes en instancias ARM.
2.4 Instalar Docker Engine, CLI y plugins
“`bash
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -y
“`
Desglose de paquetes:
| Paquete | Función |
|---|
| — | — |
|---|
| `docker-ce` | Daemon de Docker Engine (`dockerd`) |
|---|
| `docker-ce-cli` | CLI del cliente (comando `docker`) |
|---|
| `containerd.io` | Runtime de contenedores de bajo nivel (compatible con OCI) |
|---|
| `docker-buildx-plugin` | Capacidades de compilación extendidas (multiplataforma, BuildKit) |
|---|
| `docker-compose-plugin` | Compose V2 integrado como plugin de CLI de Docker |
|---|
Nota sobre `containerd.io`: No es lo mismo que el paquete `containerd` en el repositorio predeterminado de Ubuntu. El `containerd.io` de Docker es una versión específica y probada del runtime containerd. Mezclar ambos es una causa conocida de fallos al iniciar el daemon.
Paso 3: Verificar la instalación
Confirme que el daemon está activo y habilitado para iniciarse al arrancar:
“`bash
sudo systemctl status docker
sudo systemctl enable docker
“`
Verifique la versión instalada:
“`bash
sudo docker –version
sudo docker info
“`
`docker info` es más informativo que `–version` solo — revela el controlador de almacenamiento en uso (generalmente `overlay2`), el controlador cgroup (`systemd` vs `cgroupfs`), y el número de CPUs y memoria a los que Docker puede acceder.
Ejecute la prueba de humo canónica:
“`bash
sudo docker run hello-world
“`
Una ejecución exitosa imprime un mensaje “Hello from Docker!” y confirma que el daemon de Docker, la descarga de imágenes desde Docker Hub y la ejecución de contenedores funcionan correctamente.
Paso 4: Configurar Docker para acceso sin root
De forma predeterminada, el socket de Docker (`/var/run/docker.sock`) es propiedad de `root` y del grupo `docker`. Cualquier usuario que no esté en el grupo `docker` debe usar `sudo` para cada comando de Docker.
“`bash
sudo usermod -aG docker $USER
“`
Aplique la membresía de grupo sin cerrar sesión:
“`bash
newgrp docker
“`
Verifique:
“`bash
docker run hello-world
“`
Advertencia de seguridad: La membresía en el grupo `docker` es efectivamente equivalente a `sudo` sin contraseña. Un usuario en el grupo `docker` puede montar trivialmente el sistema de archivos del host en un contenedor y eludir todos los controles de acceso a nivel de sistema de archivos. En sistemas multiusuario o servidores compartidos, considere usar Docker rootless en su lugar:
“`bash
dockerd-rootless-setuptool.sh install
“`
El modo rootless ejecuta el daemon de Docker y los contenedores bajo un espacio de nombres de usuario sin privilegios, reduciendo drásticamente la superficie de ataque. Es la configuración recomendada para cualquier entorno donde múltiples usuarios comparten el mismo host.
Paso 5: Referencia de comandos esenciales de Docker
Gestión de imágenes
“`bash
Pull a specific image version from Docker Hub
docker pull nginx:1.27-alpine
List locally cached images
docker images
Remove a specific image
docker rmi nginx:1.27-alpine
Remove all dangling (untagged) images to reclaim disk space
docker image prune
Remove all unused images (not just dangling)
docker image prune -a
“`
Consejo para producción: Siempre descargue etiquetas con versión (p. ej., `nginx:1.27-alpine`) en lugar de `latest` en cualquier flujo de trabajo automatizado o de producción. La etiqueta `latest` es mutable — puede apuntar a una imagen diferente después de un push al registro, rompiendo la reproducibilidad.
Ciclo de vida de los contenedores
“`bash
Run a container interactively with a pseudo-TTY
docker run -it ubuntu:22.04 /bin/bash
Run a container in detached mode with port mapping and a name
docker run -d -p 8080:80 –name my-nginx nginx:1.27-alpine
List running containers
docker ps
List all containers (including stopped)
docker ps -a
Stop a running container gracefully (SIGTERM, then SIGKILL after timeout)
docker stop my-nginx
Start a stopped container
docker start my-nginx
Remove a stopped container
docker rm my-nginx
Force-remove a running container (sends SIGKILL immediately)
docker rm -f my-nginx
View real-time logs
docker logs -f my-nginx
Execute a command inside a running container
docker exec -it my-nginx /bin/sh
“`
Inspección de recursos y del sistema
“`bash
Display real-time resource usage statistics
docker stats
Inspect detailed container metadata (JSON)
docker inspect my-nginx
Display disk usage by Docker objects
docker system df
Remove all stopped containers, unused networks, dangling images, and build cache
docker system prune
“`
`docker system prune` es uno de los comandos de mantenimiento más importantes para servidores de larga duración. Sin una limpieza periódica, la caché de compilación de Docker y los contenedores detenidos pueden consumir decenas de gigabytes en un host de desarrollo o CI activo.
Paso 6: Docker Compose — Orquestación de aplicaciones multicontenedor
Docker Compose V2 (el `docker-compose-plugin` instalado anteriormente) se invoca como `docker compose` (con un espacio), no el legado `docker-compose` (con un guion). Ambas sintaxis funcionan si tiene el plugin instalado, pero V2 es el estándar actual.
6.1 Comprensión de la estructura del archivo Compose
Cree un directorio de proyecto y un archivo `compose.yml` (el nombre de archivo preferido en Compose V2; `docker-compose.yml` sigue siendo compatible por retrocompatibilidad):
“`bash
mkdir ~/my-web-app && cd ~/my-web-app
nano compose.yml
“`
Un ejemplo realista para producción con Nginx y un servicio backend:
“`yaml
services:
web:
image: nginx:1.27-alpine
ports:
- "8080:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./html:/usr/share/nginx/html:ro
depends_on:
- app
restart: unless-stopped
app:
image: node:20-alpine
working_dir: /usr/src/app
volumes:
- ./app:/usr/src/app
command: node server.js
environment:
- NODE_ENV=production
restart: unless-stopped
“`
Explicación de las directivas clave de Compose:
- `restart: unless-stopped` — El contenedor se reinicia automáticamente después de un fallo o reinicio del sistema, a menos que el operador lo haya detenido explícitamente. Esta es la política correcta para servicios de larga duración; `always` reiniciará incluso los contenedores detenidos intencionalmente.
- `depends_on` — Controla el orden de inicio pero no espera a que el servicio esté *listo* (p. ej., una base de datos aceptando conexiones). Para el control de disponibilidad, use `healthcheck` combinado con `condition: service_healthy`.
- `volumes` con `:ro` — Montar archivos de configuración como solo lectura evita que un proceso de contenedor comprometido modifique su propia configuración.
6.2 Comandos del flujo de trabajo de Compose
“`bash
Start all services in detached mode
docker compose up -d
View logs for all services
docker compose logs -f
View logs for a specific service
docker compose logs -f web
List running Compose services
docker compose ps
Scale a specific service to multiple replicas
docker compose up -d –scale app=3
Stop services without removing containers
docker compose stop
Stop and remove containers, networks, and volumes
docker compose down –volumes
Rebuild images before starting (useful after code changes)
docker compose up -d –build
“`
6.3 Verificar el servicio en ejecución
“`bash
curl -I http://localhost:8080
“`
Una respuesta `200 OK` confirma que Nginx está sirviendo correctamente. Para servicios que se ejecutan en una instancia remota de VPS Hosting, reemplace `localhost` con la dirección IP pública de su servidor y asegúrese de que el puerto correspondiente esté abierto en su firewall (`ufw allow 8080/tcp`).
Paso 7: Fundamentos de redes en Docker
Comprender el modelo de redes de Docker es esencial para construir aplicaciones multicontenedor que se comuniquen de forma segura.
Controladores de red predeterminados:
| Controlador | Caso de uso |
|---|
| — | — |
|---|
| `bridge` | Predeterminado para contenedores independientes; espacio de nombres de red aislado en el host |
|---|
| `host` | El contenedor comparte la pila de red del host; máximo rendimiento, cero aislamiento |
|---|
| `none` | Sin acceso a la red; útil para procesamiento por lotes o cargas de trabajo sensibles a la seguridad |
|---|
| `overlay` | Redes multihost para clústeres Docker Swarm |
|---|
| `macvlan` | Asigna una dirección MAC al contenedor; aparece como un dispositivo físico en la red |
|---|
Creación y uso de una red bridge personalizada:
“`bash
Create an isolated network
docker network create my-app-network
Run containers attached to the custom network
docker run -d –name db –network my-app-network postgres:16-alpine
docker run -d –name api –network my-app-network my-api-image
Containers on the same custom bridge network can resolve each other by name
Inside 'api', you can connect to 'db' using the hostname 'db'
“`
Las redes bridge personalizadas proporcionan resolución DNS automática entre contenedores por nombre de contenedor. La red `bridge` predeterminada no lo hace — esta es una distinción crítica que provoca fallos de conexión cuando los desarrolladores asumen que los contenedores en la red predeterminada pueden comunicarse entre sí por nombre.
Paso 8: Datos persistentes con volúmenes de Docker
Los contenedores son efímeros por diseño. Cualquier dato escrito dentro del sistema de archivos de un contenedor se pierde cuando el contenedor es eliminado. Para almacenamiento persistente, use volúmenes o bind mounts.
“`bash
Create a named volume
docker volume create pgdata
Use the volume with a container
docker run -d
–name postgres-db
-e POSTGRES_PASSWORD=securepassword
-v pgdata:/var/lib/postgresql/data
postgres:16-alpine
List volumes
docker volume ls
Inspect a volume (shows mount point on host)
docker volume inspect pgdata
Remove unused volumes
docker volume prune
“`
Volúmenes vs. bind mounts:
| Característica | Volumen con nombre | Bind Mount |
|---|
| — | — | — |
|---|
| Gestionado por Docker | Sí | No |
|---|
| Requiere ruta del host | No | Sí |
|---|
| Portable entre hosts | Sí (con controladores de volumen) | No |
|---|
| Ideal para | Datos de bases de datos, estado de aplicaciones | Código de desarrollo, archivos de configuración |
|---|
| Mecanismo de respaldo | `docker run –volumes-from` | Herramientas estándar del sistema de archivos |
|---|
Paso 9: Mantener Docker actualizado
El repositorio oficial de Docker gestiona las actualizaciones a través del mecanismo estándar `apt`:
“`bash
sudo apt update
sudo apt upgrade -y
“`
Para actualizar solo los paquetes relacionados con Docker sin actualizar todo el sistema:
“`bash
sudo apt install –only-upgrade docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
“`
Consulte las notas de la versión de Docker antes de actualizaciones de versiones principales, especialmente para cambios en el controlador de almacenamiento, el manejo de versiones de cgroup o versiones de API obsoletas que puedan afectar los archivos `compose.yml` existentes.
Docker vs. enfoques alternativos de contenedorización
| Característica | Docker Engine | Podman | LXC/LXD | containerd (independiente) |
|---|
| — | — | — | — | — |
|---|
| Arquitectura del daemon | Daemon centralizado | Sin daemon | Basado en daemon | Basado en daemon |
|---|
| Soporte rootless | Sí (v20+) | Nativo | Limitado | Sí |
|---|
| Soporte de Docker Compose | Nativo | Mediante `podman-compose` | No | No |
|---|
| Cumplimiento OCI | Sí | Sí | No (formato LXC) | Sí |
|---|
| Integración con Kubernetes | Mediante shim CRI-dockerd | CRI nativo | No | CRI nativo |
|---|
| Soporte Windows/macOS | Docker Desktop | Limitado | No | No |
|---|
| Mejor opción para | Desarrollo general y producción | Enfocado en seguridad, rootless | Contenedores de sistema, VMs | Nodos de Kubernetes |
|---|
Para equipos que ejecutan cargas de trabajo en contenedores a escala en bare metal, un entorno de Servidores Dedicados le brinda control total sobre los parámetros del kernel, la programación de I/O de almacenamiento y la configuración de red — todo lo cual afecta directamente la densidad de contenedores y el rendimiento.
Lista de verificación de fortalecimiento para producción
Antes de ejecutar Docker en un entorno de producción, aborde lo siguiente:
Configuración del daemon (`/etc/docker/daemon.json`):
“`json
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
},
"storage-driver": "overlay2",
"userns-remap": "default",
"live-restore": true,
"no-new-privileges": true
}
“`
- `log-opts` con `max-size` y `max-file`: Sin rotación de registros, los archivos de registro JSON de Docker llenarán su disco. Esta es una de las causas más comunes de interrupciones inesperadas del servidor en hosts con contenedores.
- `userns-remap: "default"`: Habilita la reasignación de espacios de nombres de usuario, de modo que el root del contenedor (UID 0) se mapea a un UID sin privilegios en el host.
- `live-restore: true`: Permite que los contenedores sigan ejecutándose si el daemon de Docker falla o se reinicia durante una actualización — crítico para el mantenimiento sin tiempo de inactividad.
- `no-new-privileges: true`: Evita que los procesos del contenedor obtengan privilegios adicionales mediante binarios `setuid` o `setgid`.
Consideraciones de red y firewall:
Docker manipula `iptables` directamente y omite las reglas de `ufw` de forma predeterminada. Un contenedor con un puerto publicado (`-p 8080:80`) será accesible desde internet incluso si `ufw deny 8080` está configurado. Para aplicar las reglas de `ufw` sobre la manipulación de `iptables` de Docker, agregue `"iptables": false` a `daemon.json` y gestione el enrutamiento manualmente, o use `–network host` de Docker con reglas `ufw` explícitas.
Para proyectos que requieren terminación HTTPS, combine su aplicación en contenedor con un proxy inverso correctamente configurado (Nginx o Traefik) y un certificado válido. Los Certificados SSL son un requisito previo para cualquier servicio web en producción que se ejecute detrás de una pila en contenedores.
Si su carga de trabajo implica inferencia de aprendizaje automático, servicio de modelos o procesamiento de datos acelerado por GPU dentro de contenedores, el NVIDIA Container Toolkit se integra directamente con Docker Engine. El GPU Hosting proporciona el hardware subyacente necesario para estas cargas de trabajo.
Para equipos que gestionan múltiples proyectos con administración de contenedores basada en web, el VPS con cPanel ofrece un entorno de panel de control administrado que puede complementar las implementaciones basadas en Docker para pilas de aplicaciones más simples.
Conclusiones técnicas clave y matriz de decisión
Cuándo usar Docker en un VPS vs. un servidor dedicado:
- Use un VPS para entornos de desarrollo, staging y cargas de trabajo de producción de tráfico bajo a medio donde la densidad de contenedores es de 10 a 50 contenedores.
- Use un servidor dedicado cuando la densidad de contenedores supere las 50 instancias, cuando necesite un rendimiento de I/O predecible (sin efecto de vecino ruidoso), o cuando se requiera ajuste de parámetros del kernel (`sysctl`) para su carga de trabajo.
Lista de verificación operativa antes de entrar en producción:
- Configure la rotación de registros en `daemon.json` (`max-size`, `max-file`)
- Habilite `live-restore` para sobrevivir a reinicios del daemon sin tiempo de inactividad de los contenedores
- Use volúmenes con nombre, no bind mounts, para datos de servicios con estado
- Fije versiones de imágenes en todos los archivos `compose.yml` — nunca use `latest` en producción
- Habilite `userns-remap` o ejecute Docker rootless en hosts multiusuario
- Audite las reglas `iptables` después de la instalación de Docker para confirmar que la política del firewall no está siendo eludida
- Establezca `restart: unless-stopped` en todos los servicios de larga duración
- Ejecute `docker system prune` en un trabajo cron programado para prevenir el agotamiento del disco
- Use redes bridge personalizadas para toda la comunicación entre contenedores — nunca dependa del bridge predeterminado para el descubrimiento de servicios
Preguntas frecuentes
¿Docker en Ubuntu usa `systemd` o `cgroupfs` como controlador cgroup de forma predeterminada?
Desde Docker Engine 20.10, el controlador cgroup predeterminado en sistemas basados en `systemd` (incluidas todas las versiones modernas de Ubuntu LTS) es `systemd`. Esto se alinea con los requisitos de Kubernetes y evita la inestabilidad causada por ejecutar dos gestores de cgroup simultáneamente. Puede verificarlo con `docker info | grep -i cgroup`.
¿Cuál es la diferencia entre `docker compose down` y `docker compose stop`?
`docker compose stop` detiene los contenedores en ejecución pero los conserva junto con sus redes asociadas. `docker compose down` detiene los contenedores y luego los elimina junto con las redes creadas por Compose. Agregar `–volumes` a `down` también elimina los volúmenes con nombre definidos en el archivo Compose — use este indicador con precaución en producción, ya que elimina permanentemente los datos persistentes.
¿Por qué Docker omite las reglas del firewall `ufw` en Ubuntu?
Docker inserta sus propias reglas `iptables` en las cadenas `DOCKER` y `DOCKER-USER`, que se evalúan antes que las reglas de la cadena `INPUT` de `ufw`. Esto significa que un puerto publicado con `-p` es accesible desde internet independientemente de la política de `ufw`. La mitigación correcta es agregar reglas directamente a la cadena `DOCKER-USER`, o vincular los puertos publicados a una interfaz específica (p. ej., `-p 127.0.0.1:8080:80`) cuando no se requiere acceso externo.
¿Cómo limito la CPU y la memoria que puede consumir un contenedor Docker?
Use indicadores de restricción de recursos en tiempo de ejecución: `docker run –memory="512m" –cpus="1.5" my-image`. En Compose, configúrelos bajo la clave `deploy.resources` (Compose V2) o las claves de nivel superior `mem_limit` y `cpus`. Sin límites, un solo contenedor descontrolado puede agotar los recursos del host y derribar todos los demás contenedores en el mismo host.
¿Puedo ejecutar Docker dentro de un contenedor Docker (Docker-in-Docker)?
Sí, pero está fuertemente desaconsejado para uso en producción. El patrón común para pipelines de CI es montar el socket Docker del host (`-v /var/run/docker.sock:/var/run/docker.sock`) en el contenedor de CI, lo que le da al contenedor control total sobre el daemon Docker del host — un riesgo de seguridad significativo. Una alternativa más segura es usar el indicador `–allow security.insecure` de BuildKit o herramientas diseñadas específicamente como Kaniko o Buildah, que construyen imágenes OCI sin requerir un daemon Docker.
