Docker en un VPS desde cero: instalación, Compose, actualizaciones y copias de seguridad de volúmenes
TL;DR
Docker en un VPS permite ejecutar sitios web, bases de datos, bots, VPN, servicios Git y otras aplicaciones en contenedores aislados, gestionando toda la infraestructura mediante archivos de Docker Compose. En esta guía preparará un servidor Ubuntu, instalará Docker Engine y Compose, desplegará una aplicación de prueba con HTTPS, configurará actualizaciones automáticas y copias de seguridad de Docker volumes.
- Para la mayoría de los proyectos pequeños es suficiente un VPS con 2 vCPU, 4 GB de RAM y un disco NVMe de 50–80 GB.
- Docker Engine se instala desde el repositorio APT oficial, mientras que Compose se utiliza como el complemento integrado
docker compose. - Todos los ajustes de la aplicación, la red, los volúmenes y las dependencias se describen en
compose.yaml. - Los secretos se almacenan en el archivo
.env, que no debe añadirse a Git. - Para los servicios públicos, Caddy permite proporcionar HTTPS cómodamente con certificados automáticos de Let’s Encrypt.
- No es necesario hacer copias de seguridad de los contenedores, sino de los datos: Docker volumes, volcados de bases de datos, configuraciones y el archivo
.env.
Qué configuramos y por qué
Docker es una plataforma para ejecutar aplicaciones en contenedores. Un contenedor incluye la aplicación, sus bibliotecas y las dependencias del sistema, pero utiliza el kernel del sistema operativo anfitrión. A diferencia de una máquina virtual, el contenedor no requiere un sistema operativo invitado independiente, se inicia rápidamente y consume menos memoria.
En la práctica, Docker en un VPS es necesario cuando el servidor debe ejecutar varios servicios sin instalar manualmente sus dependencias en el sistema. Por ejemplo, se pueden alojar simultáneamente WordPress, PostgreSQL, Redis, Mattermost, n8n, Gitea, Vaultwarden, una API en Python o Node.js, así como un proxy inverso con certificados TLS. Cada servicio obtiene un contenedor, una red y un almacenamiento persistente independientes.
En este tutorial se desplegará un entorno básico, pero cercano a uno de producción real:
- Docker Engine en Ubuntu 24.04 LTS;
- Docker Compose v2 para gestionar el stack;
- aplicación web de demostración Nginx;
- Caddy como proxy inverso y gestor automático de certificados HTTPS;
- Docker volume con nombre para los datos persistentes;
- red interna independiente de Docker;
- script de copia de seguridad de Docker volumes;
- plan para actualizar de forma segura las imágenes y los contenedores.
Qué obtendrá al final
Después de completar los pasos, tendrá un servidor en el que las aplicaciones se describen de forma declarativa. En lugar de una larga secuencia de comandos, almacenará la configuración en los archivos compose.yaml, Caddyfile y .env. Migrar el servicio a otro VPS se reducirá a copiar estos archivos, restaurar la copia de seguridad de los volúmenes y ejecutar docker compose up -d.
Este enfoque resulta especialmente útil si ejecuta servicios autoalojados. Los contenedores se pueden detener, eliminar y volver a crear sin perder los datos de los usuarios, siempre que los datos se encuentren en Docker volumes o en directorios del anfitrión montados, y no dentro del sistema de archivos del contenedor.
Docker Compose: por qué es necesario
Un contenedor se puede iniciar con el comando docker run, pero para un servicio permanente esto se vuelve rápidamente incómodo. Es necesario recordar los parámetros de los puertos, las redes, las variables de entorno, las políticas de reinicio, los volúmenes y las limitaciones de recursos. Docker Compose guarda todo esto en un archivo YAML.
Por ejemplo, la aplicación y la base de datos se pueden describir en un solo archivo. Con un único comando, Compose creará la red, preparará los volúmenes, descargará las imágenes e iniciará los servicios en el orden correcto. Esto también permite verificar la configuración en Git y facilita su comprensión para otro administrador.
Self-hosted en un VPS o servicio gestionado
| Criterio | Docker Self-hosted en un VPS | Plataforma gestionada |
|---|---|---|
| Control sobre el sistema | Total: SO, red, datos, versiones de las imágenes | Limitado por las capacidades del proveedor |
| Mantenimiento | El administrador se encarga de las actualizaciones y las copias de seguridad | La plataforma realiza parte de las tareas |
| Coste de varios servicios | A menudo es más económico en un solo VPS | Puede aumentar con cada servicio y base de datos |
| Flexibilidad | Se puede ejecutar casi cualquier imagen OCI | Depende del stack compatible |
| Tolerancia a fallos | Es necesario diseñarla por cuenta propia | A menudo está incluida en los planes más caros |
El uso independiente de Docker en un VPS es adecuado si está preparado para realizar operaciones básicas de Linux: actualizar paquetes, revisar registros, probar actualizaciones y almacenar copias de seguridad independientes. Para una aplicación crítica con requisitos de alta disponibilidad puede ser más razonable utilizar una base de datos gestionada o una plataforma en la nube. Sin embargo, para equipos pequeños, MVP, servicios personales y varias aplicaciones relacionadas, Docker Compose en un VPS sigue siendo una opción sencilla y controlable.
Qué configuración de VPS se necesita para esta tarea
Docker por sí mismo casi no determina los requisitos del servidor: los recursos son consumidos por los contenedores. La configuración mínima depende del número de aplicaciones, del tipo de base de datos, del volumen de archivos cargados y del tráfico esperado. El error principal es elegir un VPS únicamente por el número de núcleos y olvidar la RAM, los IOPS del disco y el espacio de reserva para las imágenes y las copias de seguridad.
| Escenario | CPU | RAM | Disco NVMe | Red |
|---|---|---|---|---|
| Pruebas, bot, un sitio pequeño | 1 vCPU | 2 GB | 25–40 GB | 100 Mbit/s |
| 2–5 contenedores pequeños, PostgreSQL, proxy inverso | 2 vCPU | 4 GB | 50–80 GB | 100 Mbit/s–1 Gbit/s |
| CRM, Mattermost, n8n, varios servicios web | 4 vCPU | 8 GB | 120–200 GB | 1 Gbit/s |
| Base de datos con mucha carga, compilaciones de CI, muchos usuarios | 8 vCPU o más | 16 GB o más | 300 GB o más | 1 Gbit/s o más |
Para esta guía y para la mayoría de los primeros proyectos con Docker, elija 2 vCPU, 4 GB de RAM, 60–80 GB de NVMe y un canal de al menos 100 Mbit/s. Este margen permite ejecutar un proxy inverso, uno o dos servicios web, PostgreSQL o MariaDB, y dejar espacio para las imágenes. Una de las opciones puede ser un VPS con las características indicadas, pero antes de realizar el pedido compruebe que el plan incluye una dirección IPv4 pública, acceso mediante SSH y suficiente espacio en disco.
Por qué Docker ocupa el disco rápidamente
Las imágenes están compuestas por capas. Después de varias actualizaciones y experimentos, en el servidor quedan imágenes no utilizadas, la caché de compilación y contenedores detenidos. Además, las bases de datos y los archivos de los usuarios se almacenan en volúmenes. No planifique utilizar el 100 % del disco: deje como mínimo entre un 20 y un 30 % de espacio libre; de lo contrario, PostgreSQL, el registro de Docker o la actualización del SO podrían finalizar con un error.
Es necesario comprobar periódicamente el uso del espacio:
# Показывает использование диска Docker: образы, контейнеры, volumes и build cache
docker system df
# Показывает свободное место в файловой системе VPS
df -h
Cuándo se necesita un servidor dedicado en lugar de un VPS
Un VPS es suficiente para la mayoría de las aplicaciones web, los servicios internos, las API y las bases de datos pequeñas. Un servidor dedicado se justifica cuando se requiere un rendimiento garantizado de la CPU y del disco, un volumen muy grande de almacenamiento local, una base de datos con mucha carga, compilaciones intensivas en CI o decenas de contenedores activos.
También conviene considerar un servidor dedicado para tareas con una carga alta constante: codificación de vídeo, servidores de juegos con muchos usuarios conectados, indexadores de blockchain, clústeres grandes de Elasticsearch y bases de datos con una gran cantidad de IOPS. No cambie a un servidor dedicado solo porque Docker funciona lentamente: primero compruebe los límites de RAM, la swap, la latencia del disco, la configuración de la base de datos y las métricas reales de la CPU.
Cómo elegir la ubicación
La ubicación influye en la latencia hasta los usuarios, los requisitos de almacenamiento de datos personales y la velocidad de conexión con servicios externos. Un servidor para un equipo de Europa normalmente se ubica cerca de sus integrantes; una API para clientes de una región concreta, en esa misma región. Para servicios privados, oriéntese por su propia latencia y por los requisitos legales.
Antes de publicar el sitio, asegúrese de que el dominio se puede dirigir mediante un registro A a la dirección IPv4 del VPS. Para emitir certificados automáticamente, el servidor de Caddy debe estar accesible desde Internet a través de los puertos 80 y 443. Si una red externa bloquea estos puertos o el DNS apunta a otra dirección, no se emitirá el certificado HTTPS.
Preparación del servidor
A continuación se utiliza Ubuntu Server 24.04 LTS, una opción estable para Docker en 2026. Las instrucciones también son similares para Ubuntu 22.04 LTS, Debian 12 y Debian 13, pero los nombres de los paquetes y las reglas del firewall pueden variar ligeramente. Conéctese al nuevo servidor como root solo para la configuración inicial.
Cree un usuario normal con sudo
No trabaje permanentemente como root. Cree un administrador, añádalo al grupo sudo y utilice después esta cuenta para administrar el servidor. En los ejemplos se utiliza el nombre deploy; sustitúyalo por el suyo.
# Создаёт пользователя deploy с домашним каталогом и запрашивает пароль
adduser deploy
# Даёт пользователю право выполнять административные команды через sudo
usermod -aG sudo deploy
Configure las claves SSH
En el equipo local, genere una clave Ed25519 si aún no tiene una. No transfiera el archivo de clave privada al servidor ni publique su contenido. La contraseña de la clave la protege en caso de pérdida del portátil.
# Выполняется на локальном компьютере: создаёт пару SSH-ключей Ed25519
ssh-keygen -t ed25519 -a 100 -C "admin@my-laptop"
# Копирует публичный ключ в аккаунт deploy на VPS
ssh-copy-id deploy@SERVER_IP
# Проверяет вход по ключу; после этого пароль root больше не нужен для обычной работы
ssh deploy@SERVER_IP
Abra una segunda conexión SSH y asegúrese de que el acceso mediante clave funciona antes de desactivar la autenticación mediante contraseña. De lo contrario, podría perder el acceso al servidor.
# Открывает настройки SSH-сервера
sudo nano /etc/ssh/sshd_config.d/99-hardening.conf
Añada los siguientes parámetros al archivo abierto:
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
X11Forwarding no
MaxAuthTries 3
# Проверяет синтаксис конфигурации SSH перед перезапуском
sudo sshd -t
# Применяет новую конфигурацию SSH без перезагрузки сервера
sudo systemctl reload ssh
Actualice el sistema e instale las utilidades básicas
Primero, actualice los índices de paquetes e instale las correcciones de seguridad. Después de las actualizaciones importantes del kernel, reinicie el servidor durante una ventana conveniente. Para comprobar si es necesario reiniciar, Ubuntu crea el archivo /var/run/reboot-required.
# Обновляет пакеты Ubuntu до актуальных версий
sudo apt update && sudo apt full-upgrade -y
# Устанавливает утилиты для загрузки ключей, диагностики и архивов
sudo apt install -y ca-certificates curl gnupg lsb-release nano \
unzip jq htop ncdu rsync cron fail2ban ufw
# Показывает, нужна ли перезагрузка после обновления ядра или библиотек
test -f /var/run/reboot-required && cat /var/run/reboot-required || echo "Reboot not required"
Configure el firewall
UFW bloquea las conexiones entrantes, excepto las permitidas explícitamente. Primero permita SSH y, después, HTTP y HTTPS. Si utiliza un puerto SSH no estándar, permita ese puerto antes de activar el firewall.
# Разрешает SSH, чтобы не потерять удалённый доступ
sudo ufw allow OpenSSH
# Разрешает публичный HTTP и HTTPS для Caddy и веб-приложений
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# Включает firewall и показывает активные правила
sudo ufw enable
sudo ufw status verbose
No abra el puerto de la base de datos, como el 5432 de PostgreSQL o el 3306 de MariaDB, a Internet sin una necesidad real. Los contenedores de una misma red de Docker pueden comunicarse mediante el nombre interno del servicio. Publique externamente solo el proxy inverso y los servicios que realmente deban estar disponibles para los usuarios.
Active Fail2ban
Fail2ban lee los registros de SSH y bloquea temporalmente las direcciones IP con errores de inicio de sesión repetidos. No sustituye a las claves SSH, pero reduce el ruido de los escáneres automatizados.
# Запускает Fail2ban сейчас и при каждой загрузке VPS
sudo systemctl enable --now fail2ban
# Проверяет, что SSH-jail активен и показывает заблокированные адреса
sudo fail2ban-client status sshd
Docker gestiona directamente las reglas de iptables/nftables para los puertos publicados. Por eso, no considere UFW por sí solo como un aislamiento absoluto de los contenedores. La protección principal consiste en no publicar puertos innecesarios mediante Compose, utilizar redes separadas y actualizar las imágenes con regularidad.
Instalación del software paso a paso
En Ubuntu, no instale el paquete docker.io del repositorio estándar si desea recibir versiones actualizadas de Docker Engine. Utilice el repositorio oficial de Docker. En 2026, la rama actual de Docker Engine es la 29.x, y Docker Compose se distribuye como un complemento v2 y se ejecuta mediante el comando docker compose.
Elimine los paquetes en conflicto
Si Docker ya estaba instalado en el VPS desde el repositorio del sistema o desde una configuración de prueba, elimine los paquetes en conflicto. El comando no elimina automáticamente sus volúmenes de Docker, pero en un servidor nuevo esto normalmente no tiene importancia.
# Удаляет старые или конфликтующие пакеты Docker, если они установлены
sudo apt remove -y docker.io docker-compose docker-compose-v2 \
docker-doc podman-docker containerd runc 2>/dev/null || true
Añada la clave GPG oficial y el repositorio de Docker
APT verifica la firma de los paquetes con la clave de Docker. Los comandos siguientes crean un directorio protegido para las claves, descargan la clave y determinan automáticamente la arquitectura y el nombre en clave de Ubuntu.
# Создаёт каталог для ключей сторонних APT-репозиториев
sudo install -m 0755 -d /etc/apt/keyrings
# Загружает официальный GPG-ключ репозитория Docker
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
# Делает ключ читаемым для APT
sudo chmod a+r /etc/apt/keyrings/docker.gpg
# Добавляет официальный stable-репозиторий Docker для текущей Ubuntu
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# Обновляет список пакетов после добавления нового репозитория
sudo apt update
Instale Docker Engine, Buildx y Compose
Instale los componentes en un solo conjunto. El paquete docker-buildx-plugin es necesario para las compilaciones modernas de imágenes, mientras que docker-compose-plugin añade el subcomando docker compose. El paquete containerd.io contiene el runtime de los contenedores.
# Устанавливает Docker Engine 29.x, CLI, containerd, Buildx и Compose v2
sudo apt install -y docker-ce docker-ce-cli containerd.io \
docker-buildx-plugin docker-compose-plugin
# Включает Docker и containerd в автозапуске
sudo systemctl enable --now docker containerd
# Проверяет установленные версии движка, Compose и Buildx
docker version
docker compose version
docker buildx version
Permita que el usuario trabaje con Docker sin sudo
De forma predeterminada, el socket /var/run/docker.sock está disponible para root. Añadir al usuario al grupo docker permite gestionar contenedores sin sudo. Es importante entender que un miembro de este grupo obtiene de facto acceso root al servidor, ya que puede montar directorios del sistema en un contenedor. No añada a este grupo a usuarios sin privilegios.
# Добавляет текущего пользователя в группу docker
sudo usermod -aG docker "$USER"
# Завершите SSH-сеанс и подключитесь снова, затем проверьте доступ к Docker
exit
Después de volver a conectarse, ejecute la prueba. El contenedor hello-world se descargará de Docker Hub, se ejecutará y, a continuación, finalizará.
# Проверяет, что Docker работает без sudo и умеет загружать образы
docker run --rm hello-world
# Показывает состояние службы Docker
systemctl status docker --no-pager
Compruebe los parámetros de red y active la rotación de registros
De forma predeterminada, los contenedores escriben los registros en archivos JSON del directorio /var/lib/docker/containers. Sin rotación, un servicio con errores puede llenar todo el disco. Configure un límite razonable para los contenedores nuevos. Los contenedores ya creados conservarán la configuración anterior hasta que se vuelvan a crear.
# Создаёт конфигурацию Docker daemon с ротацией container logs
sudo tee /etc/docker/daemon.json > /dev/null <<'EOF'
{
"log-driver": "local",
"log-opts": {
"max-size": "10m",
"max-file": "3"
},
"live-restore": true
}
EOF
# Проверяет JSON и перезапускает Docker для применения конфигурации
sudo jq empty /etc/docker/daemon.json
sudo systemctl restart docker
# Проверяет, что Docker снова отвечает после перезапуска
docker info --format '{{.ServerVersion}}'
El parámetro live-restore ayuda a que los contenedores que ya están en ejecución sobrevivan al reinicio del daemon de Docker. Esto no elimina la necesidad de probar las actualizaciones: los cambios de red, la actualización del kernel o una imagen incompatible aún pueden requerir una ventana de mantenimiento.
Cree la estructura de trabajo de los proyectos
No almacene los proyectos de Compose de producción en la carpeta personal de un usuario cualquiera ni en /tmp. Una ubicación adecuada es /opt/stacks. Restrinja el acceso: las configuraciones suelen contener dominios, contraseñas y claves API.
# Создаёт общий каталог для Compose-проектов
sudo mkdir -p /opt/stacks/demo-app
# Передаёт каталог пользователю deploy; замените имя при необходимости
sudo chown -R deploy:deploy /opt/stacks
# Устанавливает безопасные права на каталог проектов
chmod 750 /opt/stacks /opt/stacks/demo-app
# Переходит в каталог будущего Docker Compose-стека
cd /opt/stacks/demo-app
Configuración de Docker, Compose y HTTPS
Ahora crearemos un servicio accesible mediante un nombre de dominio a través de HTTPS. El ejemplo utiliza Nginx como una aplicación sencilla y Caddy como reverse proxy. En un proyecto real, Nginx puede sustituirse por su API, WordPress, Gitea, Nextcloud u otro contenedor.
Antes de iniciar, cree un registro DNS A, por ejemplo app.example.com, que apunte a la dirección IPv4 pública del VPS. Sustituya app.example.com y la dirección de correo electrónico por sus propios valores. Puede comprobar el DNS con el comando dig +short app.example.com: el resultado debe coincidir con la IP del servidor.
Cree el archivo de variables de entorno
El archivo .env almacena valores que no conviene duplicar en YAML: el dominio, el email para las notificaciones y los secretos. Establezca los permisos 600. Si el proyecto se almacena en Git, añada .env a .gitignore y coloque en el repositorio únicamente .env.example sin secretos reales.
# Создаёт файл окружения с параметрами конкретного сервера
cat > /opt/stacks/demo-app/.env <<'EOF'
DOMAIN=app.example.com
[email protected]
EOF
# Ограничивает чтение файла текущим владельцем
chmod 600 /opt/stacks/demo-app/.env
# Создаёт шаблон переменных для Git без приватных данных
cat > /opt/stacks/demo-app/.env.example <<'EOF'
DOMAIN=app.example.com
[email protected]
EOF
Prepare el Caddyfile
Caddy obtiene y renueva automáticamente los certificados TLS mediante Let’s Encrypt u otra autoridad de certificación ACME compatible. Para ello, los puertos 80 y 443 deben estar disponibles desde el exterior, y el DNS debe apuntar al servidor. Caddy redirigirá HTTP a HTTPS y reenviará las solicitudes al contenedor de la aplicación.
# Создаёт конфигурацию reverse proxy Caddy
cat > /opt/stacks/demo-app/Caddyfile <<'EOF'
{
email {$ACME_EMAIL}
}
{$DOMAIN} {
encode zstd gzip
reverse_proxy app:80
header {
-Server
X-Content-Type-Options "nosniff"
X-Frame-Options "SAMEORIGIN"
Referrer-Policy "strict-origin-when-cross-origin"
}
log {
output stdout
format json
}
}
EOF
Dentro de la red de Docker, Caddy se conecta al nombre app, no a una dirección IP. El DNS de Docker asigna automáticamente este nombre al contenedor del servicio. El puerto 80 de Nginx no se publica externamente: solo está disponible para el reverse proxy.
Cree compose.yaml
El archivo de Compose describe dos servicios, un volume con nombre y una red interna. La imagen de Caddy almacena los certificados en el volume caddy_data; sin él, después de recrear el contenedor, Caddy perderá los datos de ACME y podría solicitar certificados nuevos con demasiada frecuencia. El volume app_content demuestra el almacenamiento de archivos persistentes de la aplicación.
services:
caddy:
image: caddy:2.10-alpine
container_name: demo-caddy
restart: unless-stopped
env_file:
- .env
ports:
- "80:80"
- "443:443"
volumes:
- caddy_data:/data
- caddy_config:/config
- ./Caddyfile:/etc/caddy/Caddyfile:ro
networks:
- frontend
healthcheck:
test: ["CMD", "caddy", "version"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
app:
image: nginx:1.28-alpine
container_name: demo-app
restart: unless-stopped
volumes:
- app_content:/usr/share/nginx/html
networks:
- frontend
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1/ >/dev/null 2>&1"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
deploy:
resources:
limits:
memory: 256M
networks:
frontend:
driver: bridge
volumes:
caddy_data:
caddy_config:
app_content:
El campo deploy.resources.limits.memory es compatible con las versiones actuales de Docker Compose y limita la memoria del contenedor. Los límites son especialmente útiles para los servicios secundarios: un proceso con una fuga de memoria no debe consumir por completo la RAM del VPS. Para PostgreSQL, aplicaciones Java y Elasticsearch, los límites deben seleccionarse según la documentación del producto correspondiente.
Añada una página inicial al Docker volume
Un volume con nombre no se puede editar con un editor normal en el host sin buscar la ruta interna de Docker. Para la escritura inicial, puede ejecutar un contenedor temporal de Alpine y montar el volume en él. Esta técnica también resulta útil para diagnosticar los archivos de un volume.
# Переходит в каталог проекта
cd /opt/stacks/demo-app
# Проверяет итоговую конфигурацию после подстановки .env
docker compose config
# Создаёт volume и записывает в него тестовую HTML-страницу
docker run --rm -v demo-app_app_content:/data alpine:3.21 \
sh -c 'cat > /data/index.html <<EOF
<!doctype html>
<html lang="ru">
<head><meta charset="utf-8"><title>Docker VPS</title></head>
<body><h1>Docker Compose работает</h1><p>Страница отдана через Caddy и HTTPS.</p></body>
</html>
EOF'
Compose construye el nombre del volume a partir del nombre del directorio del proyecto y del nombre del volume. En este ejemplo, el directorio se llama demo-app, por lo que el resultado es demo-app_app_content. Puede comprobar el nombre exacto con el comando docker volume ls.
Inicie el stack y compruebe los contenedores
# Скачивает образы и запускает все сервисы в фоновом режиме
docker compose up -d
# Показывает состояние контейнеров, опубликованные порты и health status
docker compose ps
# Показывает последние 100 строк журналов Caddy
docker compose logs --tail=100 caddy
# Проверяет ответ reverse proxy локально с нужным Host-заголовком
curl -I -H "Host: ${DOMAIN}" http://127.0.0.1
Si el DNS ya está configurado, compruebe el certificado y el código de respuesta desde una red externa:
# Проверяет HTTPS, цепочку сертификата и HTTP-заголовки домена
curl -Iv "https://${DOMAIN}"
# Проверяет, что порт 443 действительно слушается на VPS
sudo ss -tulpn | grep ':443'
El resultado esperado es el código HTTP/2 200 o HTTP/1.1 200 OK. Durante el primer inicio, la emisión del certificado puede tardar unos segundos. Consulte docker compose logs -f caddy si Caddy informa de un problema al validar el dominio.
Comandos diarios útiles de Docker Compose
| Comando | Propósito |
|---|---|
docker compose ps |
Estado de los servicios del proyecto actual |
docker compose logs -f app |
Visualización de los registros de la aplicación en tiempo real |
docker compose restart app |
Reinicio de un solo servicio |
docker compose exec app sh |
Apertura de un shell dentro del contenedor en ejecución |
docker compose pull |
Descarga de nuevas versiones de las imágenes sin iniciarlas |
docker compose down |
Detención y eliminación de contenedores y de la red, pero no de los volumes |
No ejecute
docker compose down -ven un proyecto de producción si no comprende las consecuencias. La opción-velimina los volumes con nombre y, junto con ellos, pueden desaparecer las bases de datos, los archivos cargados y los certificados.
Copias de seguridad y mantenimiento
Un contenedor es una parte reproducible de la aplicación y normalmente no es necesario hacerle una copia de seguridad. Para la recuperación son más importantes los archivos Compose, los secretos, los datos de los volumes, los volcados de las bases de datos y las cargas de los usuarios. La regla «si hay un volume, debe haber una copia de seguridad» debe ser obligatoria para cada servicio de production.
Qué se debe incluir exactamente en la copia de seguridad
- Los archivos
compose.yaml,Caddyfile, las configuraciones de las aplicaciones y las plantillas de entorno. - El archivo
.enven un almacenamiento cifrado o estrictamente protegido. - Los Docker volumes con nombre que contienen datos de las aplicaciones.
- Los volcados lógicos de PostgreSQL, MariaDB/MySQL y otras bases de datos.
- Los directorios bind mount, si los datos están montados desde el host.
- La lista de imágenes y versiones, si utiliza compilaciones propias.
Para una base de datos es preferible realizar un volcado lógico estándar mediante pg_dump o mariadb-dump, en lugar de copiar sus archivos mientras está en funcionamiento. Un archivo de un volume activo de PostgreSQL puede resultar inconsistente. Detenga la base de datos durante la copia de seguridad de archivos o utilice las herramientas de respaldo físico recomendadas por los desarrolladores del SGBD.
Instale Restic
Restic es una utilidad práctica para realizar copias de seguridad deduplicadas, cifradas e incrementales. Admite almacenamientos compatibles con S3, SFTP, directorios locales y numerosos backend-ов en la nube. A continuación se utiliza un almacenamiento compatible con S3; las variables de acceso no deben llegar a Git ni al historial de shell.
# Устанавливает Restic из репозитория Ubuntu
sudo apt install -y restic
# Создаёт каталог для скриптов и временных архивов резервных копий
sudo mkdir -p /opt/backups/docker-volumes
sudo chown -R deploy:deploy /opt/backups
# Создаёт файл с параметрами доступа к удалённому S3-хранилищу
nano /opt/backups/restic.env
Rellene /opt/backups/restic.env con sus propios valores. Utilice un bucket independiente y claves de acceso independientes, limitadas únicamente a ese bucket. La contraseña del repositorio de Restic debe ser un valor aleatorio largo y almacenarse por separado del servidor: sin ella es imposible restaurar los datos.
export RESTIC_REPOSITORY="s3:https://s3.example.net/docker-vps-backups"
export RESTIC_PASSWORD="CHANGE_TO_A_LONG_RANDOM_RESTIC_PASSWORD"
export AWS_ACCESS_KEY_ID="CHANGE_ME"
export AWS_SECRET_ACCESS_KEY="CHANGE_ME"
# Ограничивает доступ к ключам и паролю владельцем файла
chmod 600 /opt/backups/restic.env
# Загружает переменные и инициализирует зашифрованный Restic-репозиторий один раз
source /opt/backups/restic.env
restic init
Cree un script de copia de seguridad para Docker volumes
El script siguiente archiva los volumes indicados mediante un contenedor temporal de Alpine, añade la configuración del proyecto y envía el resultado a Restic. Está destinado a volumes pequeños y medianos. Para bases de datos grandes, añada un volcado independiente de la base de datos antes de crear el archivo.
cat > /opt/backups/backup-demo-app.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
PROJECT_DIR="/opt/stacks/demo-app"
BACKUP_DIR="/opt/backups/docker-volumes"
DATE="$(date +%F_%H-%M-%S)"
ARCHIVE="${BACKUP_DIR}/demo-app-volumes-${DATE}.tar.gz"
source /opt/backups/restic.env
mkdir -p "${BACKUP_DIR}"
docker run --rm \
-v demo-app_app_content:/volumes/app_content:ro \
-v demo-app_caddy_data:/volumes/caddy_data:ro \
-v demo-app_caddy_config:/volumes/caddy_config:ro \
-v "${BACKUP_DIR}:/backup" \
alpine:3.21 \
sh -c "tar -czf /backup/$(basename "${ARCHIVE}") -C /volumes ."
restic backup \
"${ARCHIVE}" \
"${PROJECT_DIR}/compose.yaml" \
"${PROJECT_DIR}/Caddyfile" \
"${PROJECT_DIR}/.env" \
--tag docker-demo-app \
--tag "${DATE}"
rm -f "${ARCHIVE}"
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
restic check
EOF
# Делает скрипт исполняемым
chmod 700 /opt/backups/backup-demo-app.sh
# Запускает первую резервную копию вручную и проверяет отсутствие ошибок
/opt/backups/backup-demo-app.sh
En este ejemplo también se guardan los Caddy volumes. No son datos empresariales críticos, pero su respaldo reduce el número de operaciones repetidas con los certificados durante una recuperación ante desastres. Si utiliza PostgreSQL, añada antes del archivado el comando docker compose exec -T db pg_dump -U USER DATABASE > /opt/backups/db.sql e incluya el archivo de volcado en restic backup.
Configure cron
Primero ejecute el script manualmente y después añádalo a cron. No configure la automatización hasta comprobar que la primera copia de seguridad aparece en el repositorio remoto y que el comando restic snapshots muestra un snapshot.
# Открывает пользовательский crontab пользователя deploy
crontab -e
Añada una ejecución diaria a las 03:30. La redirección de la salida conserva un registro que permite diagnosticar errores.
30 3 * /opt/backups/backup-demo-app.sh >> /opt/backups/backup.log 2>&1
# Показывает созданные snapshots в удалённом репозитории
source /opt/backups/restic.env
restic snapshots
# Проверяет содержимое последнего snapshot без восстановления
restic ls latest | head -n 50
Compruebe la restauración
Una copia de seguridad que nunca se ha restaurado no puede considerarse verificada. Al menos una vez por trimestre, implemente un VPS de prueba o un directorio independiente, restaure un snapshot y asegúrese de que la aplicación se inicia. No restaure un archivo de prueba sobre un volume de production sin detener los servicios y comprender las consecuencias.
# Восстанавливает последний Restic snapshot в отдельный каталог для проверки
mkdir -p /tmp/restic-restore-test
source /opt/backups/restic.env
restic restore latest --target /tmp/restic-restore-test
# Просматривает восстановленные файлы конфигурации и архив volume
find /tmp/restic-restore-test -maxdepth 3 -type f | sort
Actualización segura de Docker y los contenedores
Separe las actualizaciones del sistema operativo, Docker Engine y las imágenes de las aplicaciones. No actualice todo simultáneamente durante el horario de trabajo. Primero lea las release notes de la aplicación, haga una copia de seguridad reciente, descargue las imágenes y después vuelva a crear los servicios. Es mejor no utilizar la etiqueta latest en production: fije una versión major o una versión concreta verificada, por ejemplo nginx:1.28-alpine.
# Переходит в каталог Compose-проекта
cd /opt/stacks/demo-app
# Создаёт ручной бэкап перед обновлением
/opt/backups/backup-demo-app.sh
# Загружает новые образы, не меняя работающие контейнеры
docker compose pull
# Показывает, какие образы теперь используются и их размеры
docker compose images
# Пересоздаёт сервисы только при изменении образа или конфигурации
docker compose up -d --remove-orphans
# Проверяет состояние и последние ошибки после обновления
docker compose ps
docker compose logs --tail=100
Para las aplicaciones stateless, la actualización suele realizarse como un procedimiento rolling: varias réplicas detrás de un reverse proxy se actualizan de forma sucesiva. Docker Compose estándar en un único VPS no proporciona un rolling update completo con las garantías de un orquestador, por lo que para un contenedor individual debe esperar una breve interrupción durante la recreación. Realice las bases de datos, las migraciones de esquema y las actualizaciones major durante una maintenance window y con un plan de rollback verificado.
# Обновляет пакеты Docker Engine и Compose из официального APT-репозитория
sudo apt update
sudo apt install --only-upgrade -y docker-ce docker-ce-cli containerd.io \
docker-buildx-plugin docker-compose-plugin
# Удаляет только неиспользуемый build cache; volumes и работающие образы не затрагиваются
docker builder prune -af
No ejecute sin criterio docker system prune -a --volumes. Este comando puede eliminar imágenes, redes y volumes no utilizados; un volume marcado por error que contenga datos importantes puede resultar irrecuperable sin una copia de seguridad. Utilice primero docker system df y elimine únicamente los recursos innecesarios concretos.
Solución de problemas + preguntas frecuentes
Docker muestra «permission denied while trying to connect to the Docker daemon socket»
El error significa que el usuario actual no tiene acceso al socket de Docker. Compruebe los grupos con el comando id: entre ellos debe aparecer docker. Si el grupo no aparece, ejecute sudo usermod -aG docker $USER, salga completamente de la sesión SSH y vuelva a conectarse. No solucione el problema utilizando permanentemente sudo chmod 666 /var/run/docker.sock: esto abre Docker a todos los usuarios del sistema y crea una grave amenaza de seguridad.
El contenedor se reinicia constantemente y en docker compose ps aparece el estado Restarting
Primero consulte el registro del servicio concreto: docker compose logs --tail=200 app. Normalmente la causa es una variable de entorno ausente, una contraseña de base de datos incorrecta, un puerto ocupado, un error de migración o una arquitectura de imagen no compatible. Compruebe la sustitución final de las variables mediante docker compose config. Compruebe también el consumo de memoria con el comando free -h y los mensajes del sistema con dmesg -T | grep -i oom: el proceso puede haber sido terminado por OOM killer.
El certificado HTTPS de Caddy no se emite o el sitio se abre mediante HTTP
Compruebe que el registro A del dominio apunta a la IP del VPS: dig +short ваш-домен. Asegúrese de que los puertos 80 y 443 están permitidos en UFW, publicados en compose.yaml y no están ocupados por otro proceso: sudo ss -tulpn | grep -E ':80|:443'. Después, consulte docker compose logs caddy. Las causas frecuentes son el proxy de DNS mediante un servicio de terceros, un registro AAAA de IPv6 con una dirección incorrecta o puertos cerrados en el nivel de red del proveedor.
El VPS se ha quedado sin espacio aunque hay pocos datos de la aplicación
Comience con df -h y docker system df -v. El último comando muestra el tamaño de las imágenes, volumes, contenedores y build cache. Compruebe los registros mediante docker compose logs y el tamaño del directorio /var/lib/docker. Si el problema está en un build cache obsoleto, es más seguro empezar con docker builder prune -af. No elimine volumes con el comando prune hasta saber a qué proyecto pertenecen y comprobar que existe una copia de seguridad reciente.
La aplicación no se conecta a PostgreSQL o Redis dentro de Compose
Los contenedores deben comunicarse entre sí mediante el nombre del servicio, por ejemplo postgres://db:5432/app, y no mediante localhost. Dentro de un contenedor, localhost apunta al propio contenedor. Compruebe que ambos servicios están en la misma red de Compose mediante docker compose exec app getent hosts db. Si la base de datos todavía se está iniciando, añada un healthcheck y una lógica de reintentos de conexión en la aplicación: depends_on no sustituye la disponibilidad de la base de datos para recibir consultas.
¿Qué configuración mínima de VPS será adecuada?
Para aprender, un sitio estático con Caddy y un bot pequeño, bastan 1 vCPU, 2 GB de RAM y 25–40 GB de SSD o NVMe. Para un proyecto pequeño real con base de datos, comience con 2 vCPU, 4 GB de RAM y 50–80 GB de NVMe. Si utiliza PostgreSQL, n8n, Mattermost, una aplicación Java o varios servicios, 8 GB de RAM serán considerablemente más fiables. Deje siempre espacio libre para Docker images, registros y copias de seguridad.
¿Qué elegir: VPS o dedicated para esta tarea?
Para Docker Compose, servicios personales, MVP, SaaS pequeños y herramientas de equipo, normalmente basta con un VPS. Se implementa más rápido, es más fácil de escalar mediante el plan y resulta más económico con una carga moderada. Un dedicated es necesario cuando existe una carga de CPU alta y sostenida, un gran número de operaciones de disco, bases de datos voluminosas, compilaciones de CI o requisitos de rendimiento garantizado. Mida primero el consumo real mediante docker stats, htop y las métricas del disco, en lugar de elegir un servidor dedicado «por si acaso».
¿Se pueden actualizar automáticamente todos los contenedores mediante Watchtower?
Técnicamente es posible, pero las actualizaciones no controladas son arriesgadas para un sistema de production. Una nueva imagen puede requerir una migración de la base de datos, cambiar las variables de entorno o contener una versión incompatible. Es más seguro configurar notificaciones sobre nuevas versiones, actualizar las imágenes manualmente durante una ventana técnica y hacer una copia de seguridad antes de recrear los contenedores. La actualización automática es aceptable para servicios stateless sin importancia, pero incluso en ese caso debe fijar las versiones de las imágenes y revisar los registros después de la actualización.
Conclusiones y próximos pasos
Ahora dispone de una arquitectura base de Docker para production en un VPS: un servidor protegido, Docker Engine, un proyecto Compose, HTTPS mediante Caddy, volumes persistentes y copias de seguridad cifradas. Esta base es suficiente para alojar la mayoría de las aplicaciones self-hosted sin instalar manualmente las dependencias en Ubuntu.
- Añada monitorización: node_exporter, cAdvisor, Uptime Kuma o un servicio externo de comprobación HTTP.
- Para cada proyecto nuevo, cree un directorio independiente en
/opt/stacks, un archivo Compose independiente, un.envindependiente y un plan de copias de seguridad independiente. - Cuando aumente la carga, traslade la base de datos a un servidor independiente o a una solución managed, configure métricas y límites de recursos, y realice restauraciones de prueba periódicas de las copias de seguridad.
La regla principal del mantenimiento sigue siendo sencilla: almacene la configuración de forma reproducible, mantenga los secretos separados y protegidos, guarde los datos en volumes o directorios explícitos y comience cada actualización con una copia de seguridad reciente y verificable.