Saltar al contenido
Suscríbete gratis

Desplegar cualquier app con Dokploy en un VPS

Despliega tu app en un VPS con Dokploy: servidor, dominio, HTTPS y despliegue automático desde GitHub.

DevOpsPrincipiante20 minLección 1 de 1Equipo de Lopand · 25 de octubre de 2026
En esta lección

Lo que vas a lograr

  • Instalar Dokploy en un VPS y darle dominio con HTTPS
  • Empaquetar tu app con Docker en una imagen ligera
  • Desplegar sola cada vez que subes a main, con GitHub Actions

Antes de empezar

  • Un VPS con Linux y acceso por SSH como root (2 GB de RAM mínimo)
  • Un dominio donde puedas crear registros DNS
  • Una cuenta de GitHub y otra de Docker Hub
  • Una app que se construya con npm run build (Astro, Vite o similar)

Al terminar este tutorial vas a tener tu app corriendo en tu propio servidor, con dominio y HTTPS. Cada cambio que subas a main se publicará solo. Es el mismo flujo con el que publicamos este sitio, academy.lopandsolutions.com.

Qué vamos a construir

  1. Un VPS con Dokploy, un panel que administra tus apps con Docker y les pone HTTPS.
  2. Una app en Dokploy apuntando a tu dominio.
  3. Dos flujos de trabajo en GitHub Actions: uno prueba y construye tu código; el otro arma una imagen de Docker, la sube a Docker Hub y le pide a Dokploy que la despliegue.

¿Por qué construir la imagen en GitHub y no en el servidor? Porque el servidor solo corre apps: si la construcción falla, falla en GitHub y tu sitio sigue arriba con la versión anterior.

Paso 1: Prepara el servidor

Según la documentación oficial, tu VPS necesita como mínimo:

  • 2 GB de RAM y 30 GB de disco. Es el mínimo para el panel; suma lo que necesiten tus apps.
  • Un sistema probado por Dokploy: Ubuntu 24.04, 23.10, 22.04, 20.04 o 18.04; Debian 12, 11 o 10; Fedora 40; o CentOS 9 u 8.

Deja abiertos estos puertos en el firewall del proveedor:

PuertoPara qué
22SSH
80 y 443Tus sitios (HTTP y HTTPS), los atiende Traefik
3000El panel de Dokploy, solo mientras le pones dominio

Paso 2: Instala Dokploy

Conéctate por SSH y ejecuta el instalador oficial:

En tu servidor
curl -sSL https://dokploy.com/install.sh | sh

Al terminar, abre http://IP-DE-TU-SERVIDOR:3000 y crea la cuenta de administrador.

Lo primero que hacemos en Lopand es darle al panel su propio subdominio con HTTPS (por ejemplo panel.tu-dominio.com). Cuando ya entres por ese dominio, cierra el acceso por IP y puerto:

En tu servidor
docker service update --publish-rm "published=3000,target=3000,mode=host" dokploy

Paso 3: Prepara tu app con un Dockerfile

Dokploy corre contenedores, así que tu app necesita un Dockerfile. Para un sitio estático (Astro, Vite, etc.) usamos dos etapas: una construye con Node y la otra sirve el resultado con nginx. La imagen final no lleva Node ni node_modules; en uno de nuestros proyectos bajó de unos 500 MB a unos 50 MB.

Necesitas tres archivos en la raíz del proyecto. Además, npm ci exige que tengas package-lock.json.

Dockerfile
FROM node:22-alpine AS construccion
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:1.29-alpine
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=construccion /app/dist /usr/share/nginx/html
EXPOSE 80
HEALTHCHECK --interval=30s --timeout=10s --start-period=20s --retries=5 \
CMD wget -q --spider http://127.0.0.1/ || exit 1
nginx.conf
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri/index.html $uri $uri/ =404;
}
error_page 404 /404.html;
}

El .dockerignore evita mandarle a Docker lo que no hace falta:

.dockerignore
node_modules
dist
.git

Una lección que nos costó un despliegue: en el HEALTHCHECK usa 127.0.0.1, no localhost. Dentro del contenedor, localhost también resuelve a la dirección IPv6 ::1. Este nginx no escucha ahí, así que la revisión de salud falla siempre y el contenedor queda marcado como no saludable.

Paso 4: Crea la app y apunta tu dominio

  1. En tu proveedor de DNS crea un registro A con tu subdominio (por ejemplo app.tu-dominio.com) apuntando a la IP del servidor.

  2. En Dokploy crea un proyecto y, dentro, una aplicación. En General → Provider elige Docker y escribe el nombre de tu imagen en Docker Hub (por ejemplo tu-usuario/mi-app). Si tu imagen es privada, llena también Username y Password con tu usuario de Docker Hub y un token de acceso; si es pública, déjalos vacíos.

    Pestaña General de una aplicación en Dokploy con la fuente Docker seleccionada y el campo Docker Image
    En General → Provider elige Docker y escribe el nombre completo de tu imagen.
  3. En la pestaña Domains pulsa Add Domain: tu subdominio, ruta /, puerto 80 (el de nginx dentro del contenedor), HTTPS activado y certificado Let’s Encrypt.

    Pestaña Domains de una aplicación en Dokploy con el dominio, el puerto 80, HTTPS y el certificado de Let's Encrypt
    Así queda el dominio de academy.lopandsolutions.com: puerto 80, HTTPS y certificado de Let's Encrypt.
  4. Copia el ID de la aplicación: es el último segmento de la URL del panel cuando estás dentro de ella (…/services/application/<ID>). Lo vas a necesitar en GitHub.

Paso 5: Despliegue automático desde GitHub

Usamos dos flujos de trabajo. El de CI prueba y construye en cada push. El de CD solo corre cuando el CI terminó bien en main, así que nunca se despliega código que no pasó las pruebas.

.github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run build

Si tu proyecto tiene pruebas, agrega aquí un paso - run: npm test.

.github/workflows/cd.yml
name: CD
on:
workflow_run:
workflows: ["CI"]
types: [completed]
branches: [main]
jobs:
deploy:
if: github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.workflow_run.head_sha }}
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64
push: true
tags: tu-usuario/mi-app:build-${{ github.run_number }}
- name: Desplegar en Dokploy
env:
API: https://panel.tu-dominio.com/api
KEY: ${{ secrets.DOKPLOY_API_KEY }}
APP: ${{ vars.DOKPLOY_APP_ID }}
IMAGEN: tu-usuario/mi-app:build-${{ github.run_number }}
run: |
: "${APP:?Falta la variable DOKPLOY_APP_ID}"
: "${KEY:?Falta el secreto DOKPLOY_API_KEY}"
curl --fail-with-body -sS -X POST "$API/application.update" \
-H "x-api-key: $KEY" -H "Content-Type: application/json" \
-d "{\"applicationId\":\"$APP\",\"dockerImage\":\"$IMAGEN\"}"
curl --fail-with-body -sS -X POST "$API/application.deploy" \
-H "x-api-key: $KEY" -H "Content-Type: application/json" \
-d "{\"applicationId\":\"$APP\"}"

Detalles que evitan horas de depuración:

  • workflows: ["CI"] se refiere al name: del otro flujo, no al nombre del archivo. Si no coinciden, el CD nunca arranca y GitHub no te avisa.
  • cd.yml tiene que estar en tu rama principal. Los flujos que se disparan con workflow_run solo corren desde la rama por defecto del repositorio.
  • tu-usuario en la etiqueta es el mismo que DOCKERHUB_USERNAME (o una organización donde tengas permiso). Si no, Docker Hub rechaza el push con denied.
  • Una etiqueta nueva en cada despliegue (build-123, no latest). Con una etiqueta que no cambia, Docker puede no volver a bajar la imagen y tu contenedor arranca con la versión vieja. El nombre da igual mientras cambie: en la captura de arriba ves latest-5, que es la forma que usamos en Lopand.
  • --fail-with-body en curl. Sin él, un error de la API de Dokploy deja el flujo en verde.
  • Las dos primeras líneas de run detienen el despliegue con un mensaje claro si olvidaste dar de alta la variable o el secreto.

Da de alta los secretos y la variable desde la terminal, dentro de la carpeta de tu repositorio (o agrega -R tu-usuario/tu-repo a cada comando). Cada gh secret set te pide el valor:

En tu computadora
gh secret set DOCKERHUB_USERNAME
gh secret set DOCKERHUB_TOKEN
gh secret set DOKPLOY_API_KEY
gh variable set DOKPLOY_APP_ID --body "ID-DE-TU-APP"

La llave de Dokploy se genera en el panel, en Profile → API/CLI Keys → Generate New Key (la ruta es /settings/profile). Dale un nombre que diga para qué es, por ejemplo github-actions-mi-app, y si quieres, una fecha de vencimiento en Expiration para obligarte a rotarla.

Diálogo Generate API Key de Dokploy con los campos Name, Prefix, Expiration y Organization
El diálogo para crear la llave. Con un nombre basta; los límites son opcionales.

La API vive en https://panel.tu-dominio.com/api y se autentica con el encabezado x-api-key.

Paso 6: Comprueba que quedó

Haz un push a main. Primero corre el CI y, cuando termina bien, arranca el CD:

En tu computadora
gh run list --workflow=CD --limit 1
gh run watch
curl -sI https://app.tu-dominio.com | head -1

Si ves HTTP/2 200 (o HTTP/1.1 200 OK), tu app ya está en producción con HTTPS. Desde ahora, cada push a main la actualiza sola.

Conclusión

Ya tienes un servidor propio con Dokploy, tu app empaquetada en una imagen ligera y un despliegue que corre solo cada vez que subes a main, sin pasar código que no se construyó.

En la siguiente lección de la ruta hacemos el CI más rápido y usamos ejecutores propios para no gastar los minutos del plan de GitHub.

Equipo de Lopand

Escribimos sobre lo que construimos para nuestros clientes y nuestros juegos. Si algo no te funcionó, escríbenos y lo corregimos.

Un correo a la semana, sin relleno

Lo nuevo de la Academy y lo que estamos construyendo en Lopand.