Tutorial

Desplegar Strapi en un VPS: la guía completa

Desarrollo12 min de lectura14 pasos

Strapi es el CMS headless de código abierto de referencia en Node.js. Autoalojarlo en un VPS le da la propiedad total de sus contenidos, de su base de datos y de sus archivos subidos — sin cuota de suscripción, sin dependencia de Strapi Cloud y sin plataforma de terceros entre sus equipos y sus datos. Esta guía cubre la totalidad del despliegue: generación del proyecto, Dockerfile multietapa, docker-compose con PostgreSQL, secretos de producción, Nginx, HTTPS, almacenamiento S3, copias de seguridad automatizadas y actualizaciones sin roturas.

Contenido· Strapi Cloud o autoalojamiento: lo que pierde, lo que gana1/10
  1. 01Strapi Cloud o autoalojamiento: lo que pierde, lo que gana
  2. 02Ventajas de autoalojar Strapi en un VPS
  3. 03Requisitos previos: hardware, software y dominio
  4. 04Preparar el proyecto antes de tocar el servidor
  5. 05Desplegar la pila en el VPS
  6. 06Sacar los medios del disco: el provider S3
  7. 07Automatizar la copia de seguridad de la base
  8. 08Actualizar Strapi sin romper las migraciones
  9. 09Solución de problemas: los errores más frecuentes
  10. 10Strapi y un frontend Nuxt o Next.js en el mismo VPS

Strapi Cloud o autoalojamiento: lo que pierde, lo que gana

Strapi Cloud es práctico para empezar, pero impone restricciones que se vuelven rápidamente bloqueantes en producción real. El plan Free limita los tipos de contenido y los usuarios administradores; los planes de pago facturan según el número de peticiones a la API y de archivos subidos. Sobre todo, sus datos residen en la infraestructura de Strapi Inc. — algo incompatible con los requisitos de localización de datos de muchos clientes (RGPD estricto, contratos sectoriales, datos sensibles). Al autoalojar en un VPS, recupera la propiedad completa de la base PostgreSQL, la elección del almacenamiento (disco local o bucket compatible con S3), la libertad de instalar cualquier plugin de la comunidad y volúmenes de contenido que ya no dependen de una tabla de precios. La contrapartida es operativa — usted gestiona las actualizaciones, las copias de seguridad y la monitorización. Esta guía le muestra exactamente cómo hacerlo.

Ventajas de autoalojar Strapi en un VPS

  • Propiedad de los datos — su base PostgreSQL y sus archivos subidos permanecen en su VPS, bajo su control exclusivo
  • Ningún cupo de plan — el número de entradas, de tipos de contenido y de cuentas de administrador ya no lo fija una suscripción, sino los recursos de su VPS
  • Plugins de la comunidad — cualquier plugin npm se instala libremente, sin validación ni lista blanca de la plataforma
  • API en su dominio — REST y GraphQL expuestos en cms.mondomaine.com, sin intermediario ni límite de tasa impuesto
  • Pila unificada — Strapi y un frontend Nuxt o Next.js conviven en el mismo VPS, detrás de un único reverse proxy
  • Localización de los datos — esencial para el cumplimiento del RGPD y los requisitos contractuales de sus clientes
  • Coste previsible — el VPS tiene una tarifa fija mensual, independiente del volumen de contenido o del tráfico de la API
  • Control de las migraciones — usted elige cuándo y cómo aplicar las actualizaciones de Strapi

Requisitos previos: hardware, software y dominio

Strapi consume más recursos que la mayoría de los frameworks porque el build del admin React consume por sí solo más de 2 GB de RAM. Un VPS de 1 GB de RAM fallará sistemáticamente por OOM durante la fase de build — no lo infradimensione. Objetivos mínimos: 2 vCPU, 4 GB de RAM. En producción estable, una vez terminado el build, la aplicación funciona con holgura con 512 MB a 1 GB. En el plano del software: Docker 24+ y Docker Compose v2 (docker compose, no docker-compose), Git para recuperar su código en el servidor, y Certbot para el TLS. En cuanto a la base de datos: se recomienda PostgreSQL 16 en producción — SQLite es funcional en desarrollo, pero demasiado limitado bajo carga concurrente e incompatible con algunos plugins. En cuanto a la red: apunte cms.mondomaine.com a la IP de su VPS antes de empezar, y prevea 20 GB de disco como mínimo (node_modules, archivos subidos, volcados de copia de seguridad).

Preparar el proyecto antes de tocar el servidor

  1. Generar el proyecto Strapi

    En su equipo, genere el proyecto con el comando oficial npx create-strapi-app@latest mon-cms --dbclient=postgres. Elija TypeScript si su equipo lo domina. Inicialice el repositorio Git, súbalo a su forja y luego clónelo en el VPS en /srv/mon-cms. Todo lo que sigue se prepara en ese repositorio y no en el servidor: el VPS solo debe recibir código ya versionado.

  2. Conectar la configuración de base de datos a PostgreSQL

    En config/database.js — o config/database.ts en TypeScript —, la función exportada recibe env y devuelve un objeto connection. Declare client: 'postgres' y, a continuación, seis claves leídas desde el entorno en el subobjeto connection: host: env('DATABASE_HOST', '127.0.0.1'), port: env.int('DATABASE_PORT', 5432), database: env('DATABASE_NAME', 'strapi'), user: env('DATABASE_USERNAME', 'strapi'), password: env('DATABASE_PASSWORD', '') y ssl: env.bool('DATABASE_SSL', false). El segundo argumento de env() no es más que un valor de reserva para el desarrollo: ningún valor real debe figurar en este archivo, que acaba en Git.

  3. Generar los cinco secretos de producción

    Strapi se niega a arrancar en producción sin APP_KEYS, API_TOKEN_SALT, ADMIN_JWT_SECRET, JWT_SECRET y TRANSFER_TOKEN_SALT. Genere cada uno con openssl rand -base64 32; APP_KEYS espera varias, separadas por comas, así que produzca al menos dos. Escríbalos en /srv/mon-cms/.env en el VPS — nunca en Git — junto con DATABASE_HOST, DATABASE_NAME, DATABASE_USERNAME, DATABASE_PASSWORD, NODE_ENV=production y URL=https://cms.mondomaine.com. Una clave que falte se traduce en un rechazo de arranque, no en una advertencia.

  4. Escribir el Dockerfile multietapa

    Dos etapas bastan para mantener ligera la imagen de producción. La etapa builder parte de node:20-alpine, fija WORKDIR /app, copia package*.json, lanza npm ci, copia el resto del código y luego compila el admin con NODE_ENV=production npm run build. La etapa runner parte de la misma imagen base y solo copia de builder tres cosas: la carpeta de build, node_modules y package.json. Termine con EXPOSE 1337 y un CMD que llame a npm run start — es el script de arranque que Strapi instala, no intente lanzar un archivo de servidor a mano. Si el build muere por OOM, ponga NODE_OPTIONS=--max-old-space-size=4096 antes de npm run build.

Desplegar la pila en el VPS

  1. Describir los servicios en docker-compose.yml

    El archivo declara dos servicios y un volumen con nombre. El servicio postgres utiliza la imagen postgres:16-alpine, recibe POSTGRES_DB, POSTGRES_USER y POSTGRES_PASSWORD en su bloque environment, monta el volumen pgdata en /var/lib/postgresql/data y pasa a restart: unless-stopped. El servicio strapi se construye desde el Dockerfile local con build: ., lee sus variables mediante env_file: .env, declara depends_on: postgres, monta ./public/uploads en /app/public/uploads y publica su puerto con ports: 127.0.0.1:1337:1337. Esta vinculación al bucle local es el punto importante: Nginx se convierte en el único punto de entrada público, el puerto 1337 nunca se expone al exterior. Un servicio redis sigue siendo opcional, para la caché de sesiones o las colas de procesamiento.

  2. Construir la imagen y arrancar la pila

    Lance docker compose build y luego docker compose up -d. Siga el arranque con docker compose logs -f strapi. Strapi aplica sus migraciones de esquema en el primer arranque en cuanto NODE_ENV=production está puesto. Cuente de dos a cinco minutos: la construcción del admin React es con diferencia la etapa más lenta. Espere la línea Strapi started successfully antes de continuar.

  3. Poner Nginx como reverse proxy

    Cree el vhost /etc/nginx/sites-available/cms.mondomaine.com. Escucha en listen 80 sobre server_name cms.mondomaine.com, lleva un client_max_body_size 50M — imprescindible para las subidas de medios — y un único bloque location / que hace proxy_pass http://127.0.0.1:1337. Añádale las cuatro cabeceras que Strapi espera detrás de un proxy: Host, X-Real-IP, X-Forwarded-For y X-Forwarded-Proto, cada una puesta por una directiva proxy_set_header. Active el vhost con un enlace simbólico hacia sites-enabled, y luego valide y recargue con nginx -t && systemctl reload nginx.

  4. Activar HTTPS y fijar la variable URL

    Obtenga el certificado con certbot --nginx -d cms.mondomaine.com: Certbot reescribe el vhost para redirigir HTTP hacia HTTPS. Compruebe después que URL=https://cms.mondomaine.com figura correctamente en el .env, sin barra final. Es esta variable la que Strapi utiliza para construir los enlaces de los medios y las redirecciones del admin; sin ella, los archivos subidos salen con direcciones falsas y el panel de administración se comporta mal detrás del proxy. Reinicie el contenedor tras cualquier modificación del .env: las variables se leen al arrancar.

  5. Comprobar que la instancia responde de verdad

    Abra https://cms.mondomaine.com/admin y cree la primera cuenta de administrador — Strapi la pide en el primer acceso y no volverá a pedirla. Controle después tres puntos: docker compose ps muestra los dos contenedores en estado running, la API pública responde en /api, y el envío de un archivo de prueba desde la mediateca funciona. Un 502 Bad Gateway en esta fase indica casi siempre un contenedor strapi detenido o todavía en construcción: relea docker compose logs strapi antes de tocar Nginx.

Sacar los medios del disco: el provider S3

Por defecto, Strapi guarda los archivos subidos en public/uploads, en el disco del VPS. El montaje ./public/uploads del docker-compose hace que sobrevivan a una recreación de contenedor, pero crecen con la mediateca, entran en cada copia de seguridad y desaparecen con el servidor. El provider oficial resuelve los tres problemas: npm install @strapi/provider-upload-aws-s3. Declárelo después en config/plugins.js, bajo la clave upload y luego config: provider: 'aws-s3' y un objeto providerOptions que lee cuatro valores del entorno — accessKeyId: env('AWS_ACCESS_KEY_ID'), secretAccessKey: env('AWS_ACCESS_SECRET'), region: env('AWS_REGION') y params: { Bucket: env('AWS_BUCKET') }. Cualquier almacenamiento compatible con S3 sirve: Scaleway Object Storage, Wasabi, Cloudflare R2. Dos trampas merecen conocerse antes del cambio. Los medios ya subidos no se migran solos — cambie el provider antes de la puesta en línea, o copie a mano el contenido de public/uploads al bucket. Y las URLs de los medios cambian de dominio: si su frontend las almacena en caché o las reescribe, verifíquelo tras el cambio.

Automatizar la copia de seguridad de la base

  1. Crear la carpeta de destino

    En el VPS, mkdir -p /srv/backups/strapi. Mantenga esta carpeta fuera del repositorio Git y fuera de cualquier ruta montada en un contenedor: un volcado nunca debe acabar en una imagen ni en una release.

  2. Localizar el nombre real del contenedor PostgreSQL

    docker compose ps da el nombre exacto, de la forma mon-cms-postgres-1. Deriva del nombre de la carpeta del proyecto: no lo copie de una guía, léalo en su máquina. Un script de copia de seguridad que apunta a un contenedor inexistente falla en silencio una vez colocado en el cron.

  3. Escribir el comando de volcado

    Basta con una sola línea: docker exec mon-cms-postgres-1 pg_dump -U strapi strapi | gzip > /srv/backups/strapi/strapi-$(date +%Y%m%d).sql.gz. El pg_dump se ejecuta en el contenedor; la compresión y la escritura se hacen en el host. Láncelo una primera vez a mano y compruebe el tamaño del archivo producido: un volcado de unos pocos bytes indica un error de autenticación tragado por la tubería.

  4. Purgar los volcados demasiado antiguos

    Añada a continuación find /srv/backups/strapi -name '*.sql.gz' -mtime +7 -delete. Sin esta línea, el disco se llena en unas semanas: es la avería más banal de una copia de seguridad diaria — la base cae porque la copia ha saturado el volumen.

  5. Planificar y luego sacar los volcados del servidor

    Ponga los dos comandos en un script /etc/cron.daily/strapi-backup, con #!/bin/bash en la primera línea y un chmod +x para hacerlo ejecutable. Añádale un rsync hacia un almacenamiento externo, o un envío al mismo bucket S3 que los medios. Una copia de seguridad que se queda en la máquina que respalda no protege de nada; pruebe una restauración completa al menos una vez antes de depender de ella.

Actualizar Strapi sin romper las migraciones

Una actualización se pilota desde el repositorio, no en el servidor: cambie la versión en package.json, suba los cambios y luego, en el VPS, encadene git pull y docker compose build && docker compose up -d. Strapi detecta y aplica las migraciones de esquema al arrancar mientras NODE_ENV=production esté puesto; confírmelo con docker compose logs strapi | grep -i migrat. Tres precauciones merecen el rodeo. Tome un pg_dump justo antes de la subida de versión, y no solo el de la noche anterior — una migración fallida se repara con una restauración, nunca con un segundo intento. Lea las notas de versión de los plugins que tenga instalados: una subida mayor de Strapi rompe más a menudo un plugin de la comunidad que el propio núcleo. Por último, si una migración falla, no relance la pila en bucle: cada reinicio repite la misma migración sobre una base ya modificada a medias. Detenga los contenedores, lea el registro completo, restaure el volcado si es necesario y luego corrija.

Solución de problemas: los errores más frecuentes

OOM durante el build (Killed o JavaScript heap out of memory): el build del admin React supera la memoria disponible. Dos remedios — añadir NODE_OPTIONS=--max-old-space-size=4096 en la etapa builder del Dockerfile, o aumentar temporalmente el swap del VPS con fallocate -l 2G /swapfile, chmod 600 /swapfile, mkswap /swapfile y luego swapon /swapfile. Si el VPS sigue bloqueado, construya la imagen en una máquina más potente y súbala a un registro.

Cannot find module @strapi/plugin-*: nunca comparta la carpeta node_modules entre un entorno de desarrollo local y la imagen de producción mediante un volumen Docker. El node_modules local está compilado para su sistema operativo, no para el Alpine Linux del contenedor. Elimine todo volumen node_modules del docker-compose y deje que el npm ci del Dockerfile se encargue.

URL mismatch en el admin, o medios con ruta relativa: la variable URL del .env debe corresponder exactamente a la dirección pública HTTPS de su Strapi, sin barra final. Cualquier divergencia rompe los enlaces de los medios subidos y provoca errores CORS en el panel de administración.

413 Request Entity Too Large: la directiva client_max_body_size de Nginx es demasiado baja. Súbala a 50M como mínimo, o a 100M si sube vídeos, y luego recargue Nginx.

password authentication failed for user al arrancar: la contraseña del .env cambió después de la creación del volumen pgdata. PostgreSQL solo relee su contraseña al inicializar el volumen; alinee el .env con la contraseña existente, o parta de un volumen nuevo tras haber respaldado la base.

Tres reglas no negociables para una instancia Strapi en producción: S3 para los archivos subidos (@strapi/provider-upload-aws-s3) — sus medios sobreviven a cualquier recreación de contenedor y no engordan el disco del VPS; pg_dump diario automatizado copiado fuera del servidor — una copia de seguridad solo local desaparece con el VPS; NODE_ENV=production imperativamente — el modo desarrollo recompila el admin en caliente, expone el Content-Type Builder y desactiva las optimizaciones de caché, algo que hay que proscribir en producción.

Strapi y un frontend Nuxt o Next.js en el mismo VPS

Es perfectamente posible hacer convivir Strapi y un frontend en el mismo VPS, siempre que haya suficiente RAM — cuente 8 GB para los dos, ya que ambos builds pueden dispararse al mismo tiempo. La arquitectura más simple utiliza Nginx como despachador: las peticiones hacia cms.mondomaine.com se proxian hacia el puerto 1337 (Strapi), y las dirigidas a mondomaine.com hacia el puerto 3000 (Nuxt o Next.js). Strapi expone su API REST en /api — el frontend la consume directamente en la red Docker interna, sin pasar por Nginx, lo que reduce la latencia. Si su frontend genera páginas estáticas (nuxt generate, o next build en exportación estática), Nginx puede servir los archivos desde el disco y proxiar solo las rutas dinámicas. Esta arquitectura monolítica es ideal para un proyecto de tamaño medio — un solo servidor, un solo certificado TLS, un solo punto de supervisión. Si prefiere separar el frontend, nuestras guías de despliegue de Nuxt y Node.js retoman el mismo método en un segundo VPS Cloud dimensionado para el build.

Aloje su CMS Strapi con total autonomía

El VPS Cloud de ServOrbit aporta la RAM necesaria para el build del admin de Strapi y un entorno Docker + PostgreSQL listo para usar, para conservar la plena propiedad de sus contenidos y de sus datos.

¿Necesita ayuda?

Consulte nuestro centro de ayuda y nuestra FAQ, o contacte con nuestro equipo: llamada, WhatsApp o correo electrónico. Soporte en francés, inglés y árabe.

Escribir por WhatsAppse abre en una pestaña nueva