Hosting Equipo Avantys 7 min

Docker en VPS: Guía Completa para Principiantes

Aprende a usar Docker en tu VPS. Instalación, comandos esenciales, Docker Compose, desplegar aplicaciones y mejores prácticas.

// Compartir

Docker en VPS: Guía Completa para Principiantes
Docker en VPS

“Funciona en mi máquina” es cosa del pasado. Docker empaqueta tu aplicación con todas sus dependencias, garantizando que funcione igual en desarrollo y en producción.

Esta guía te enseña Docker desde cero, orientado a desplegar aplicaciones en tu VPS.

¿Qué es Docker y por qué usarlo?

Conceptos de Docker

Docker en una frase

Docker empaqueta tu aplicación + dependencias en un contenedor que funciona igual en cualquier lugar.

Sin Docker vs Con Docker

Sin DockerCon Docker
”Funciona en mi máquina”Funciona igual en todos lados
Instalar dependencias manualmenteDependencias incluidas
Conflictos entre versionesCada app aislada
Configuración diferente por servidorMismo contenedor siempre
Difícil de replicarUn comando para desplegar

Conceptos clave

ConceptoQué esAnalogía
ImagenPlantilla inmutableReceta de cocina
ContenedorInstancia de una imagenPlato preparado
DockerfileInstrucciones para crear imagenPasos de la receta
Docker ComposeOrquestar múltiples contenedoresMenú completo
VolumenDatos persistentesIngredientes guardados

Instalación de Docker

Ubuntu/Debian

# Instalar dependencias
sudo apt update
sudo apt install -y ca-certificates curl gnupg

# Añadir repositorio oficial
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

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 -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# Verificar
docker --version

Usar Docker sin sudo

sudo usermod -aG docker $USER
# Cerrar sesión y volver a entrar
# O ejecutar:
newgrp docker

# Verificar
docker run hello-world

Comandos esenciales

Imágenes

# Descargar imagen
docker pull nginx
docker pull node:20
docker pull mysql:8

# Ver imágenes descargadas
docker images

# Eliminar imagen
docker rmi nginx

Contenedores

# Ejecutar contenedor
docker run nginx

# Ejecutar en background (-d) con nombre
docker run -d --name mi-nginx nginx

# Ver contenedores activos
docker ps

# Ver todos (incluidos parados)
docker ps -a

# Parar contenedor
docker stop mi-nginx

# Iniciar contenedor parado
docker start mi-nginx

# Eliminar contenedor
docker rm mi-nginx

# Eliminar contenedor activo
docker rm -f mi-nginx

Puertos y volúmenes

# Mapear puerto: -p host:contenedor
docker run -d -p 8080:80 --name web nginx
# Acceder en http://IP:8080

# Montar volumen: -v host:contenedor
docker run -d -p 8080:80 \
    -v /var/www/html:/usr/share/nginx/html \
    --name web nginx

# Volumen persistente (Docker lo gestiona)
docker run -d \
    -v mis-datos:/var/lib/mysql \
    --name db mysql:8

Logs y terminal

# Ver logs
docker logs mi-nginx
docker logs -f mi-nginx  # Seguir en tiempo real

# Entrar al contenedor
docker exec -it mi-nginx bash
docker exec -it mi-nginx sh  # Si no tiene bash

# Ejecutar comando
docker exec mi-nginx nginx -t

Tu primer Dockerfile

Arquitectura Docker en VPS

Estructura básica

# Imagen base
FROM node:20-alpine

# Directorio de trabajo
WORKDIR /app

# Copiar archivos de dependencias
COPY package*.json ./

# Instalar dependencias
RUN npm ci --only=production

# Copiar código
COPY . .

# Puerto que expone
EXPOSE 3000

# Comando para ejecutar
CMD ["node", "server.js"]

Construir y ejecutar

# Construir imagen
docker build -t mi-app:1.0 .

# Ver imagen creada
docker images | grep mi-app

# Ejecutar
docker run -d -p 3000:3000 --name mi-app mi-app:1.0

Dockerfile para PHP

FROM php:8.2-fpm

# Extensiones PHP
RUN docker-php-ext-install pdo pdo_mysql mysqli

# Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

WORKDIR /var/www/html

COPY . .

RUN composer install --no-dev --optimize-autoloader

EXPOSE 9000

CMD ["php-fpm"]

Dockerfile para Python

FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8000

CMD ["gunicorn", "--bind", "0.0.0.0:8000", "app:app"]

Docker Compose

¿Por qué Docker Compose?

Cuando necesitas múltiples contenedores (app + base de datos + cache), Docker Compose los gestiona todos con un archivo.

Estructura básica

# docker-compose.yml
version: '3.8'

services:
  web:
    image: nginx
    ports:
      - "80:80"
    volumes:
      - ./html:/usr/share/nginx/html
    
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
    depends_on:
      - db
      - redis
    
  db:
    image: mysql:8
    environment:
      MYSQL_ROOT_PASSWORD: secret
      MYSQL_DATABASE: app
    volumes:
      - db-data:/var/lib/mysql
    
  redis:
    image: redis:alpine

volumes:
  db-data:

Comandos Docker Compose

# Iniciar todo
docker compose up -d

# Ver logs
docker compose logs -f

# Parar todo
docker compose down

# Reconstruir
docker compose build
docker compose up -d --build

# Ver estado
docker compose ps

# Ejecutar comando en servicio
docker compose exec app bash

Ejemplos prácticos

WordPress con Docker Compose

# docker-compose.yml
version: '3.8'

services:
  wordpress:
    image: wordpress:latest
    ports:
      - "80:80"
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: wordpress_pass
      WORDPRESS_DB_NAME: wordpress
    volumes:
      - wp-content:/var/www/html/wp-content
    depends_on:
      - db
    restart: unless-stopped

  db:
    image: mysql:8
    environment:
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: wordpress_pass
      MYSQL_ROOT_PASSWORD: root_pass
    volumes:
      - db-data:/var/lib/mysql
    restart: unless-stopped

volumes:
  wp-content:
  db-data:
docker compose up -d
# WordPress disponible en http://IP

Node.js + MongoDB

version: '3.8'

services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      MONGODB_URI: mongodb://mongo:27017/app
    depends_on:
      - mongo
    restart: unless-stopped

  mongo:
    image: mongo:6
    volumes:
      - mongo-data:/data/db
    restart: unless-stopped

volumes:
  mongo-data:

Laravel + MySQL + Redis

version: '3.8'

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    volumes:
      - .:/var/www/html
    depends_on:
      - db
      - redis

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - .:/var/www/html
      - ./nginx.conf:/etc/nginx/conf.d/default.conf
    depends_on:
      - app

  db:
    image: mysql:8
    environment:
      MYSQL_DATABASE: laravel
      MYSQL_USER: laravel
      MYSQL_PASSWORD: secret
      MYSQL_ROOT_PASSWORD: root
    volumes:
      - db-data:/var/lib/mysql

  redis:
    image: redis:alpine

volumes:
  db-data:

Redes en Docker

Red por defecto

Docker Compose crea una red automática donde los servicios se comunican por nombre.

# app puede conectar a "db" y "redis" por nombre
services:
  app:
    environment:
      DB_HOST: db
      REDIS_HOST: redis
  db:
    image: mysql:8
  redis:
    image: redis

Redes personalizadas

services:
  app:
    networks:
      - frontend
      - backend
  
  nginx:
    networks:
      - frontend
  
  db:
    networks:
      - backend

networks:
  frontend:
  backend:

Volúmenes y persistencia

Tipos de volúmenes

services:
  db:
    volumes:
      # Volumen nombrado (Docker lo gestiona)
      - db-data:/var/lib/mysql
      
      # Bind mount (carpeta del host)
      - ./backup:/backup
      
      # Archivo específico
      - ./my.cnf:/etc/mysql/my.cnf:ro

volumes:
  db-data:

Backup de volúmenes

# Listar volúmenes
docker volume ls

# Backup de volumen
docker run --rm \
    -v mi-volumen:/source:ro \
    -v $(pwd):/backup \
    alpine tar czf /backup/volumen-backup.tar.gz -C /source .

# Restaurar
docker run --rm \
    -v mi-volumen:/target \
    -v $(pwd):/backup \
    alpine tar xzf /backup/volumen-backup.tar.gz -C /target

Desplegar en producción

Variables de entorno

# docker-compose.prod.yml
services:
  app:
    env_file:
      - .env.production
    environment:
      - NODE_ENV=production
# .env.production
DATABASE_URL=mysql://user:pass@db:3306/app
REDIS_URL=redis://redis:6379
SECRET_KEY=tu-secreto-muy-largo

Reinicio automático

services:
  app:
    restart: unless-stopped
    # Opciones: no, always, on-failure, unless-stopped

Healthchecks

services:
  app:
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

Límites de recursos

services:
  app:
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 512M
        reservations:
          memory: 256M

SSL con Traefik

Traefik como reverse proxy

version: '3.8'

services:
  traefik:
    image: traefik:v2.10
    command:
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
      - "[email protected]"
      - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - letsencrypt:/letsencrypt

  app:
    image: mi-app
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.app.rule=Host(`tudominio.com`)"
      - "traefik.http.routers.app.entrypoints=websecure"
      - "traefik.http.routers.app.tls.certresolver=letsencrypt"

volumes:
  letsencrypt:

Comandos de limpieza

# Eliminar contenedores parados
docker container prune

# Eliminar imágenes sin usar
docker image prune

# Eliminar volúmenes sin usar
docker volume prune

# Eliminar todo lo no usado
docker system prune -a

# Ver espacio usado
docker system df

Preguntas frecuentes

¿Docker consume muchos recursos en un VPS pequeño?

Docker tiene overhead mínimo. Un VPS de 2GB puede correr varios contenedores pequeños. El consumo depende más de tus aplicaciones que de Docker mismo.

¿Puedo usar Docker junto con servicios instalados normalmente?

Sí, Docker coexiste con servicios nativos. Solo asegúrate de no tener conflictos de puertos. Es común tener Nginx nativo como proxy y apps en Docker.

¿Cómo actualizo una aplicación en Docker?

Construye nueva imagen con docker build, para el contenedor antiguo, e inicia el nuevo. Con Docker Compose: docker compose pull && docker compose up -d.

¿Los datos se pierden si elimino un contenedor?

Sí, a menos que uses volúmenes. Los datos en volúmenes persisten aunque elimines el contenedor. Siempre usa volúmenes para bases de datos.

¿Docker o instalación tradicional para producción?

Ambos son válidos. Docker facilita despliegues consistentes y portabilidad. La instalación tradicional puede ser más simple para una sola aplicación.

Nuestra recomendación

Para empezar:

  1. Instala Docker
  2. Prueba con docker run nginx
  3. Crea tu primer Dockerfile
  4. Usa Docker Compose para multi-contenedor

Para producción:

  • Docker Compose con restart policies
  • Volúmenes para datos persistentes
  • Traefik o Nginx para SSL
  • Backups de volúmenes

¿Necesitas ayuda con Docker? La administración gestionada de Avantys incluye configuración y mantenimiento de contenedores.


Conclusión

Docker simplifica despliegues y garantiza consistencia entre entornos. Una vez que lo dominas, no querrás volver a instalar dependencias manualmente.

Empieza con contenedores simples y ve añadiendo complejidad según necesites.

¿Necesitas un VPS para Docker? Explora los VPS de Avantys con recursos dedicados para contenedores.


¿Quieres que lo hagamos por ti?

En Avantys gestionamos tu web, hosting y crecimiento digital de punta a punta. Tú a lo importante.

Hablar con Avantys
// Boletín

Suscríbete al boletín

Guías nuevas, sin spam. Cancela cuando quieras.