* notes / devops

Dokploy: cómo alojar múltiples proyectos en un mismo servidor

Cómo pasé mis proyectos de DigitalOcean a un VPS con Dokploy: Docker Swarm por debajo, Traefik enrutando todo, y lo que aprendí para que un solo servidor aguante varias aplicaciones.

February 25, 20267 min read#devops#dokploy#docker#self-hosting

Contexto: ¿qué es Dokploy, en una frase?

Dokploy es una plataforma de despliegue open source que instalas en tu propio servidor. La experiencia se parece mucho a Vercel o Heroku, pero todo corre en tu máquina. Por debajo no hay magia: es una capa amable sobre Docker Swarm (con opción de Kubernetes si necesitas algo más grande), enrutamiento HTTP y TLS automático con Traefik, una interfaz web para manejar proyectos, dominios, variables de entorno y bases de datos, y build packs como Nixpacks que detectan el lenguaje del repo y construyen la imagen sin que escribas un Dockerfile.

La promesa es simple: conectas tu repositorio, indicas el dominio, pulsas Deploy y Dokploy clona el código, lo construye, crea un servicio en Swarm, registra las rutas en Traefik y pide el certificado a Let's Encrypt. Cuando todo sale bien, el ciclo completo tarda menos de un minuto.

Anatomía del montaje: un VPS, una entrada, muchos servicios

UsuariosVPS (Ubuntu + Docker Swarm)Traefik :80/:443routers + TLS ACMEblog.dominio.com→ contenedor next-blog:3000api.dominio.com→ contenedor go-api:8080tienda.dominio.com→ contenedor remix-shop:3000Postgres (servicio)volumen persistenteRedis (servicio)cache compartido

El esquema lo dice casi todo: el VPS expone los puertos 80 y 443 a través de un único Traefik; cada aplicación se publica como un servicio Docker con etiquetas que Traefik lee para crear sus routers; y los datos persistentes (Postgres, Redis) van en servicios separados con volúmenes nombrados. El aislamiento entre proyectos es lógico, no físico: comparten kernel y red, pero cada contenedor tiene su propio sistema de archivos y su propia configuración en Dokploy.

Implementación: del repositorio al dominio

Con cada proyecto nuevo sigo la misma secuencia. Primero, escribo un Dockerfile sencillo si hace falta; si es una app Next.js estándar, dejo que Nixpacks se encargue. Para Next.js con output: 'standalone', este Dockerfile mínimo me ha funcionado bien:

FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
 
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED=1
RUN corepack enable && pnpm build
 
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
EXPOSE 3000
CMD ["node", "server.js"]

Segundo, creo el proyecto en la interfaz de Dokploy, lo conecto al repositorio de GitHub (un webhook se encarga de los redeploys), configuro el dominio blog.dominio.com y añado las variables de entorno. Al pulsar Deploy, Dokploy genera y aplica un stack de Swarm con etiquetas de Traefik como estas:

services:
  blog:
    image: registry.dokploy.internal/blog:sha-abc123
    networks: [dokploy-network]
    deploy:
      labels:
        - "traefik.enable=true"
        - "traefik.http.routers.blog.rule=Host(`blog.dominio.com`)"
        - "traefik.http.routers.blog.entrypoints=websecure"
        - "traefik.http.routers.blog.tls.certresolver=letsencrypt"
        - "traefik.http.services.blog.loadbalancer.server.port=3000"

Esas etiquetas son el idioma común del montaje: cualquier despliegue futuro, pase o no por Dokploy, solo tiene que respetarlas para integrarse en el mismo Traefik. Tercero, si el proyecto necesita base de datos, creo un servicio gestionado desde la interfaz (Postgres, MySQL, MongoDB o Redis). En realidad es otro contenedor con un volumen persistente, y me da una cadena de conexión interna tipo postgres://user:pass@blog-db:5432/blog que solo funciona dentro de la red Docker del proyecto.

Variables, secretos y la disciplina de los entornos

Dokploy separa las variables de entorno de las variables de construcción: las primeras se inyectan al ejecutar; las segundas, durante el docker build. Parece un detalle menor, pero evita que un secreto quede guardado en una capa de la imagen y termine publicado en un registro por error. Para cada proyecto con varios entornos (desarrollo, staging, producción) creo entornos separados con sus dominios y variables, y sigo una regla fija: los secretos viven en Dokploy, nunca en el repositorio, ni siquiera cifrados.

Copias de seguridad: la parte aburrida que te salva

El error más caro en este montaje es asumir que el VPS va a estar ahí para siempre. No es así. Para las bases de datos, Dokploy hace copias automáticas hacia almacenamiento compatible con S3 (yo uso Backblaze B2 por precio); las tengo diarias, con catorce días de retención. Para los volúmenes de aplicación (imágenes subidas por usuarios, archivos estáticos) tengo una tarea programada que sincroniza con rclone a otro bucket. Y una vez al mes hago el ejercicio incómodo de restaurar una copia en una máquina virtual desechable, solo para confirmar que las copias funcionan de verdad.

La trampa: meter demasiadas cosas en muy poco

Una vez que el primer proyecto funciona, es tentador seguir metiendo aplicaciones hasta saturar el VPS. Lo descubrí con la sexta: una de las apps empezó a tener picos de latencia raros, y docker stats mostró que un build de otra app se estaba comiendo toda la CPU y dejaba al resto peleando por lo que quedaba. Lo resolví por tres frentes:

  1. Limitar los recursos de cada servicio con deploy.resources.limits, usando valores realistas de cpus y memory.
  2. Mover los builds pesados a una máquina aparte (un runner de GitHub Actions con docker buildx) que publica la imagen en un registro; Dokploy solo hace pull and run.
  3. Reservar el primer núcleo y al menos un giga de RAM para el sistema operativo y para Traefik, porque de él depende todo lo demás.

Hallazgos

Después de varios meses con esto en producción, esto es lo que aprendí:

  1. El ahorro es real, pero pide disciplina. Dejas de pagar facturas infladas y empiezas a pagar con tu tiempo si no automatizas copias, monitoreo y rollbacks.
  2. Traefik es el corazón de todo. Cuando algo no responde, la respuesta casi siempre está en sus logs: un certificado caducado, una etiqueta mal escrita, un router duplicado.
  3. Los builds largos no pueden vivir en la misma máquina que las apps sensibles a latencia. Separarlos, incluso en una infraestructura pequeña, se nota enseguida.
  4. Las plataformas gestionadas siguen teniendo sentido en algunos casos. Si un proyecto necesita despliegues preview por rama, edge functions y CDN global, pagarle a quien hace eso por oficio es razonable. No todo se mide en dólares al mes; también cuenta el tiempo que no gastas operando servidores.

Closure

Dokploy no es magia: es software que automatiza lo aburrido del self-hosting sin esconder las decisiones importantes. Para mí fue el punto medio que buscaba: la independencia de un VPS sin tener que configurar Traefik a mano cada vez que agrego un dominio. Si llevas tiempo pagando facturas que no van con tu tráfico real, pruébalo. La primera tarde es intensa; las siguientes, pura rutina.