Despliegue con Docker Compose en VPS
Guía completa para desplegar aplicaciones con Docker Compose en un VPS. Desde la instalación hasta configuraciones multi-contenedor con redes y volúmenes.
Despliegue con Docker Compose en VPS
Docker Compose es la forma más sencilla de desplegar y gestionar aplicaciones en tu VPS. Si buscas el mejor VPS para Docker, consulta primero nuestra comparativa. Luego define toda tu stack en un solo archivo, despliega con un comando y actualiza sin tiempo de inactividad.
Por qué Importa Esto
Los despliegues tradicionales son un dolor de cabeza:
- Conflictos de dependencias entre aplicaciones
- El síndrome de “funciona en mi máquina”
- Configuración manual compleja para cada servidor
- Los rollbacks requieren oración y suerte
Docker Compose resuelve esto:
- Entornos aislados - Las apps no pueden romperse entre sí
- Despliegues reproducibles - La misma configuración, el mismo resultado, siempre
- Control de versiones - Tu infraestructura es código
- Rollbacks sencillos - La versión anterior está a un comando de distancia
Requisitos Previos
- Un VPS con Ubuntu 22.04+ (recomendamos Hostinger VPS por sus imágenes optimizadas para Docker)
- Conocimientos básicos de línea de comandos
- Acceso SSH a tu servidor
Paso 1: Instalar Docker
# Eliminar versiones antiguas
sudo apt remove docker docker-engine docker.io containerd runc
# Instalar dependencias
sudo apt update
sudo apt install ca-certificates curl gnupg -y
# Añadir la clave GPG de Docker
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
# Añadir el repositorio
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
# Instalar Docker
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -y
# Añadir tu usuario al grupo docker (requiere cerrar y volver a iniciar sesión)
sudo usermod -aG docker $USER
# Verificar la instalación
docker --version
docker compose version
Cierra sesión y vuelve a iniciar para que los cambios de grupo surtan efecto.
Paso 2: Crear la Estructura de tu Proyecto
mkdir -p ~/apps/myapp
cd ~/apps/myapp
Estructura recomendada:
myapp/
├── docker-compose.yml # Archivo compose principal
├── docker-compose.prod.yml # Overrides de producción
├── .env # Variables de entorno (¡nunca confirmes en git!)
├── .env.example # Plantilla para variables de entorno
├── nginx/
│ └── nginx.conf # Configuración personalizada de Nginx
├── data/ # Datos persistentes (en .gitignore)
└── logs/ # Logs de la aplicación (en .gitignore)
Paso 3: Escribir tu Primer docker-compose.yml
Vamos a desplegar una stack web completa:
# docker-compose.yml
services:
app:
image: node:20-alpine
working_dir: /app
volumes:
- ./src:/app
- /app/node_modules
command: npm start
environment:
- NODE_ENV=production
- DATABASE_URL=postgres://user:pass@db:5432/myapp
depends_on:
db:
condition: service_healthy
restart: unless-stopped
networks:
- internal
db:
image: postgres:16-alpine
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: myapp
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d myapp"]
interval: 5s
timeout: 5s
retries: 5
restart: unless-stopped
networks:
- internal
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./certbot/conf:/etc/letsencrypt:ro
- ./certbot/www:/var/www/certbot:ro
depends_on:
- app
restart: unless-stopped
networks:
- internal
redis:
image: redis:7-alpine
volumes:
- redis_data:/data
command: redis-server --appendonly yes
restart: unless-stopped
networks:
- internal
volumes:
postgres_data:
redis_data:
networks:
internal:
driver: bridge
Paso 4: Usar Variables de Entorno Correctamente
Crea tu archivo .env:
# .env
DB_PASSWORD=your-super-secret-password-here
REDIS_PASSWORD=another-secret
API_KEY=your-api-key
Crea .env.example como documentación:
# .env.example
DB_PASSWORD=
REDIS_PASSWORD=
API_KEY=
¡Nunca confirmes .env en git! Añádelo al .gitignore:
echo ".env" >> .gitignore
echo "data/" >> .gitignore
echo "logs/" >> .gitignore
Paso 5: Overrides de Producción
Crea un archivo específico para producción:
# docker-compose.prod.yml
services:
app:
deploy:
resources:
limits:
cpus: '1'
memory: 512M
reservations:
cpus: '0.5'
memory: 256M
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
db:
deploy:
resources:
limits:
cpus: '2'
memory: 1G
Despliega con:
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
Paso 6: Comandos Comunes de Docker Compose
# Iniciar todos los servicios
docker compose up -d
# Ver logs
docker compose logs -f
# Ver logs de un servicio específico
docker compose logs -f app
# Detener todos los servicios
docker compose down
# Detener y eliminar volúmenes (¡CUIDADO - borra los datos!)
docker compose down -v
# Reconstruir y reiniciar
docker compose up -d --build
# Reiniciar un servicio específico
docker compose restart app
# Ver contenedores en ejecución
docker compose ps
# Ejecutar un comando en el contenedor
docker compose exec app sh
# Ver uso de recursos
docker stats
Paso 7: Despliegues sin Tiempo de Inactividad
Para actualizar sin interrupciones:
# Descargar nuevas imágenes
docker compose pull
# Recrear solo los contenedores modificados
docker compose up -d --no-deps app
O usa actualizaciones graduales con múltiples réplicas:
services:
app:
deploy:
replicas: 2
update_config:
parallelism: 1
delay: 10s
Paso 8: Verificaciones de Salud
Añade siempre health checks:
services:
app:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
Comprueba el estado de salud:
docker compose ps
docker inspect --format='{{json .State.Health}}' container_name
Paso 9: Gestionar Secretos
Para datos sensibles, usa secretos de Docker o gestores de secretos externos:
services:
app:
secrets:
- db_password
- api_key
environment:
- DB_PASSWORD_FILE=/run/secrets/db_password
secrets:
db_password:
file: ./secrets/db_password.txt
api_key:
file: ./secrets/api_key.txt
Paso 10: Estrategia de Copias de Seguridad
Crea un script de backup:
#!/bin/bash
# backup.sh
BACKUP_DIR="/backups/$(date +%Y-%m-%d)"
mkdir -p "$BACKUP_DIR"
# Hacer backup de PostgreSQL
docker compose exec -T db pg_dump -U user myapp > "$BACKUP_DIR/db.sql"
# Hacer backup de volúmenes
docker run --rm \
-v myapp_postgres_data:/data:ro \
-v "$BACKUP_DIR":/backup \
alpine tar czf /backup/postgres_data.tar.gz /data
# Hacer backup de Redis
docker compose exec -T redis redis-cli BGSAVE
docker cp "$(docker compose ps -q redis)":/data/dump.rdb "$BACKUP_DIR/"
echo "Backup completado: $BACKUP_DIR"
Ejemplos del Mundo Real
WordPress con Base de Datos
services:
wordpress:
image: wordpress:latest
ports:
- "8080:80"
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: ${WP_DB_PASSWORD}
WORDPRESS_DB_NAME: wordpress
volumes:
- wordpress_data:/var/www/html
depends_on:
- db
restart: unless-stopped
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: ${WP_DB_PASSWORD}
volumes:
- db_data:/var/lib/mysql
restart: unless-stopped
volumes:
wordpress_data:
db_data:
Aplicación JavaScript Full-Stack
services:
frontend:
build: ./frontend
ports:
- "3000:3000"
environment:
- REACT_APP_API_URL=http://api:4000
depends_on:
- api
api:
build: ./api
ports:
- "4000:4000"
environment:
- DATABASE_URL=postgres://user:pass@db:5432/app
- REDIS_URL=redis://redis:6379
depends_on:
- db
- redis
db:
image: postgres:16-alpine
volumes:
- pgdata:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: pass
POSTGRES_USER: user
POSTGRES_DB: app
redis:
image: redis:7-alpine
volumes:
- redisdata:/data
volumes:
pgdata:
redisdata:
Git Autoalojado con Gitea
services:
gitea:
image: gitea/gitea:latest
ports:
- "3000:3000"
- "222:22"
volumes:
- gitea_data:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
environment:
- USER_UID=1000
- USER_GID=1000
- GITEA__database__DB_TYPE=postgres
- GITEA__database__HOST=db:5432
- GITEA__database__NAME=gitea
- GITEA__database__USER=gitea
- GITEA__database__PASSWD=${DB_PASSWORD}
depends_on:
- db
restart: unless-stopped
db:
image: postgres:16-alpine
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
POSTGRES_USER: gitea
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: gitea
restart: unless-stopped
volumes:
gitea_data:
postgres_data:
Buenas Prácticas
- Fija las versiones de las imágenes - Usa
postgres:16-alpine, nopostgres:latest - Usa archivos .env - Mantén los secretos fuera de los archivos compose (consulta nuestra guía de seguridad para VPS)
- Volúmenes con nombre para los datos - No uses bind mounts para bases de datos
- Health checks en todos los servicios - Sabe cuándo los servicios están realmente listos
- Límites de recursos - Evita que contenedores desbocados maten tu servidor
- Límites de logs - Establece max-size para evitar que se llene el disco
- Usa redes - Aísla los servicios que no necesitan comunicarse entre sí
- depends_on con condiciones - Espera a que los servicios estén sanos, no solo iniciados
Errores Comunes que Debes Evitar
❌ Usar el tag latest - Las builds se vuelven irreproducibles
❌ Almacenar datos en contenedores - Los datos desaparecen cuando se elimina el contenedor
❌ Confirmar archivos .env - Los secretos quedan en el historial de git para siempre
❌ Sin health checks - depends_on no espera a que la app esté lista
❌ Ignorar los logs - Llenarán tu disco sin límites configurados
❌ Exponer puertos de base de datos - Solo expón lo que necesita acceso externo
❌ Ejecutar como root - Usa la directiva USER en los Dockerfiles
❌ Sin política de reinicio - Los contenedores no vuelven tras un fallo
Consejos para Depurar
# Ver por qué falla un contenedor
docker compose logs app --tail=100
# Abrir una shell en un contenedor en ejecución
docker compose exec app sh
# Abrir una shell en un contenedor detenido
docker compose run app sh
# Inspeccionar detalles del contenedor
docker inspect $(docker compose ps -q app)
# Comprobar conectividad de red
docker compose exec app ping db
# Ver variables de entorno
docker compose exec app env
Preguntas Frecuentes
¿Cuánta RAM necesito?
Para proyectos pequeños, 2 GB suele ser suficiente. Cada contenedor tiene su overhead, así que calcula ~100 MB por contenedor más las necesidades reales de la app. Los planes de Hostinger VPS comienzan en 4 GB, lo que maneja la mayoría de las stacks con comodidad.
¿Debo usar Docker Compose o Kubernetes?
Docker Compose para despliegues en un solo servidor (la mayoría de la gente). Kubernetes cuando necesitas clústeres multi-nodo, autoescalado o tienes un equipo de DevOps dedicado. No lo compliques más de lo necesario.
¿Cómo actualizo una aplicación en ejecución?
# Descargar las últimas imágenes
docker compose pull
# Recrear los contenedores modificados
docker compose up -d
Para builds personalizadas: docker compose up -d --build
¿Puedo usar Docker Compose con Nginx Proxy Manager?
¡Sí! No expongas los puertos directamente, simplemente pon los contenedores en la misma red que NPM. Consulta nuestra guía de proxy inverso.
¿Cómo persisto los datos?
Usa volúmenes con nombre (Docker gestiona la ubicación) o bind mounts (tú especificas la ruta). Los volúmenes con nombre son los recomendados para bases de datos.
¿Cuál es la diferencia entre up y start?
up crea e inicia los contenedores. start solo inicia contenedores existentes que estén detenidos. Usa siempre up -d.
Próximos pasos: Configura copias de seguridad automatizadas para proteger tus datos de Docker y añade monitorización para controlar la salud de los contenedores.
Ready to get started?
Get the best VPS hosting deal today. Hostinger offers 4GB RAM VPS starting at just $4.99/mo.
Get Hostinger VPS — $4.99/mo// up to 75% off + free domain included
// related topics
// related guides
$1 VPS Hosting 2026: Cheapest VPS Servers Starting at $1/Month
Looking for $1 VPS hosting? Compare the cheapest VPS providers starting from $1-3/month. Real specs, no hidden fees, honest reviews of budget VPS options.
tutorialCaddy Reverse Proxy Guide 2026: Automatic HTTPS Made Easy
Set up Caddy as a reverse proxy with automatic HTTPS, zero-config SSL, and simple Caddyfile syntax. Complete VPS deployment guide.
tutorialCloudflare Tunnel VPS Guide 2026: Expose Services Without Opening Ports
Set up Cloudflare Tunnel on your VPS to expose web apps securely without opening ports or revealing your server IP. Complete guide with Docker and DNS config.
tutorialCoolify VPS Setup Guide 2026: Self-Hosted Vercel Alternative
Deploy Coolify on your VPS for a self-hosted Vercel/Netlify experience. Complete setup guide with Docker, SSL, and app deployments.
Andrius Putna
I am Andrius Putna. Geek. Since early 2000 in love tinkering with web technologies. Now AI. Bridging business and technology to drive meaningful impact. Combining expertise in customer experience, technology, and business strategy to deliver valuable insights. Father, open-source contributor, investor, 2xIronman, MBA graduate.
// last updated: February 6, 2026. Disclosure: This article may contain affiliate links.