Saltar al contenido principal

Vite (con build)

Objetivo: desplegar una app Vite + React desde GitLab, con build en el servidor, dominio, HTTPS y auto-deploy. Requiere: Conectar GitLab y Sitio estático. Resultado: https://testvitehtml.inmersys.dev sirviendo el dist/ compilado por NGINX.

ℹ️ Aquí el build ocurre en el servidor. Si el droplet se queda sin RAM (exit code 137) o quieres deploys de segundos, usa Build local + Registry: misma imagen, pero compilada en tu máquina.


⚠️ Cambio de enfoque: Docker en vez de Nixpacks

Antes esta guía usaba Nixpacks. Ya no. Ahora el build se hace con un Dockerfile propio en el repo, sirviendo el dist/ con NGINX.

Por qué se cambió:

Problema con NixpacksCon Docker + NGINX
Elegía Node 18 por default → build roto (styleText)Fijas la versión en el FROM (node:22)
Servía con Caddy → fallback SPA sin resolver (404 al recargar rutas)NGINX con try_files ... /index.html → SPA resuelto
Contrato implícito (infería todo)Control total y explícito del build y del serve
Config vive fuera del repoTodo (Dockerfile, nginx.conf) commiteado → repo autosuficiente

Diferencias con el estático

EstáticoVite (Docker)
Build TypeStaticDockerfile
¿Construye?npm ci + npm run build (dentro del contenedor)
Sirve conNGINX (Dokploy)NGINX (nuestro, con fallback SPA)
Versión de Nodeirrelevantefijada en el FROM del Dockerfile

1. DNS

Registro A para testvitehtml → IP del droplet. Verificar con dig +short testvitehtml.inmersys.dev.


2. Archivos en el repositorio

Con el enfoque Docker, tres archivos viven en la raíz del repo. Este es el corazón de la guía.

⚠️ dist/ NO se commitea (está en el .gitignore de Vite). El build lo genera el contenedor. Si dist/ se coló al repo:

git rm -r --cached dist && git commit -m "chore: ignore dist" && git push

Dockerfile

Build multi-stage: primero compila con Node, luego copia solo el dist/ a una imagen NGINX ligera.

# ---- Build stage ----
FROM node:22-alpine AS build

WORKDIR /app

# Instala dependencias con cache eficiente
COPY package.json package-lock.json ./
RUN npm ci

# Copia el resto y compila
COPY . .
RUN npm run build

# ---- Serve stage ----
FROM nginx:1.27-alpine AS serve

# Config de nginx con fallback SPA
COPY nginx.conf /etc/nginx/conf.d/default.conf

# Copia el build de Vite (carpeta dist)
COPY --from=build /app/dist /usr/share/nginx/html

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

Claves:

  • node:22-alpine → fija la versión de Node. Adiós al error de Node 18. (Ajusta el número a lo que pida tu engines.)
  • Multi-stage → la imagen final es solo NGINX + dist/, sin node_modules (mucho más liviana).
  • COPY package*.json antes que COPY . . → aprovecha la cache de Docker: si no cambian las deps, no reinstala.

nginx.conf

Config de NGINX. Lo importante es el fallback SPA (try_files ... /index.html), que resuelve el 404 al recargar rutas internas de React Router.

server {
listen 80;
server_name _;

root /usr/share/nginx/html;
index index.html;

# Fallback SPA: cualquier ruta que no sea un archivo real
# sirve index.html para que React Router maneje el enrutado.
# Equivalente al "_redirects /* /index.html 200" de Netlify.
location / {
try_files $uri $uri/ /index.html;
}

# Cache agresivo para los assets con hash de Vite (immutable)
location /assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}

# No cachear el index.html para que siempre tome la versión nueva
location = /index.html {
add_header Cache-Control "no-cache";
}

# Compresión
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml;
gzip_min_length 256;
}

Claves:

  • try_files $uri $uri/ /index.html → el fallback SPA. Sin esto, /mi-ruta recargada da 404.
  • /assets/ con cache immutable → los archivos de Vite llevan hash en el nombre, se pueden cachear un año.
  • index.html con no-cache → cada deploy toma la versión nueva sin recargas forzadas.

.dockerignore

Evita copiar basura al contexto de build (más rápido, imagen más limpia).

node_modules
dist
.git
.gitignore
Dockerfile
.dockerignore
*.log
.env.local
.vscode
README.md

3. Configuración en Dokploy

Create Service → Application (nombre: vitejs1).

General → Provider

CampoValor
Providertab Gitlab 🦊 → Dokploy DEV
Repository / Branchdokploy-test / build/vite1
Build Path/ (donde vive el Dockerfile y el package.json)

General → Build Type

Selecciona Dockerfile (ya no Nixpacks).

CampoValor
Build TypeDockerfile
Docker File(vacío → usa Dockerfile de la raíz)
Docker Context Path(vacío → usa .)
Docker Build Stage(vacío → construye el último stage, serve)

Los tres campos de abajo (Docker File, Context Path, Build Stage) se dejan vacíos: los defaults (Dockerfile, ., último stage) son justo lo que necesitamos. Solo se tocan en monorepos o Dockerfiles con stages nombrados que quieras targetear.

Domains

Host testvitehtml.inmersys.dev · Path / · Container Port 80 (⚠️ viene 3000; NGINX escucha en 80) · HTTPS ON · Let's Encrypt.


4. Auto-deploy

Procedimiento general en Auto-deploy con webhooks. Lo específico: el wildcard pattern del webhook es build/vite1 (la branch de esta Application), y el nombre en GitLab debe distinguirlo del resto (dokploy dev build vite 1).

Sin webhook: git push → General → botón Deploy (siempre clona el último commit de la branch y reconstruye la imagen).


Errores de esta fase

SíntomaCausaSolución
SyntaxError: ... 'styleText'Node viejo en el FROMSubir la versión: FROM node:22-alpine
No encuentra package.jsonBuild Path en /dist o Dockerfile mal ubicadoBuild Path = /, Dockerfile en la raíz
404 al recargar rutas internasFalta el fallback SPAtry_files $uri $uri/ /index.html; en nginx.conf
App no responde / 502Container Port malPonerlo en 80 (NGINX escucha en 80)
Build lentísimo / imagen giganteSin .dockerignoreAgregar .dockerignore (excluir node_modules, dist, .git)
Cambios no se ven tras deployindex.html cacheadono-cache en location = /index.html

Errores generales en Errores comunes.