Guía Fullstack VPS de Hostinger: React, Express, Docker y CI/CD

Angela Sofíá Osorio

Angela Sofíá Osorio

Tiempo de lectura 10 minutes

Fecha de publicación

Desplegar una aplicación fullstack moderna no debería ser una pesadilla de configuraciones manuales. Como JavaScript/TypeScript Developer, sé que tener un pipeline de despliegue limpio, predecible y automatizado es esencial para llevar nuestros proyectos de entorno local a producción sin fricciones.

Para este tutorial, utilizaremos un VPS de Hostinger. Después de probar múltiples proveedores, Hostinger ofrece el equilibrio perfecto entre rendimiento, control total (acceso root) y facilidad de uso, lo que lo hace ideal para orquestar contenedores.

Nota especial: Si vas a seguir este tutorial y aún no tienes tu servidor, puedes usar mi enlace de afiliado para obtener un 20% de descuento adicional en la contratación de tu VPS.

En esta guía, desplegaremos un frontend en React (Vite) y un backend en Node.js (Express) utilizando Docker, configuraremos un proxy inverso con Nginx y certificados SSL, y automatizaremos todo el proceso utilizando GitHub Actions.

Requisitos Previos

Antes de comenzar, asegúrate de contar con lo siguiente:

¿Qué es un VPS?

Un VPS o Virtual Private Server (Maquina Virtual privada) es precisamente un servidor cuyos recursos están dedicados a ti. Mientras que la mayoría de Hostings compartidos siempre comparten el sistema operativo y hardware con otros usuarios, lo cual te limita para poder instalar tus propios programas o comandos.
Imagina que el Hosting compartido es como vivir con Roomies, tu rentas un departamento con otras personas y todos comparten los mismos espacios. El VPS es como tener tu propio departamento, aunque compartes la misma infraestrructura, el Edificio, nadie puede entrar y usar tu departamento. El VPS te da acceso al root, lo cual te permite instalar tu propio software y servicios.

Primeros pasos

Una vez que realices el pago de tu VPS en Hostinger. vamos a elegir Ubuntu Server

Al hacer esto se nos pedirá que creemos una contraseña y tendremos la posivbilidad de conectarnos vía SSH. No te preocupes, esto último, lo haremos más adelante.

A partir de ahora Hostinger te ofrecera servixcios adicionales que puedes o NO agregar, solo dale siguiente.

Si no habías elegido un plan y realizado tu pago, en esta parte te pedira que elijas uno, los meses a comprar y que realices el pago:

Una vez hecho el pago verás que Hostinger se pondrá a configurar tu VPS por lo que puedes salir de la página y Hostinger te enviará un email cuando termine(Igual puedes quedarte y esperar). Tardará aproximadamente 3 minutos.

Conectar nuestro SSH a la terminal

Ahora que Hostinger ha terminado de configurar nuestro VPS verás que nos muestra una bienvenida y la clave ssh. con ello avanzamos a la siguiente fase (Nota que no he censurado la clave ssh. Esto es porque al final dell tutorial eliminé por completo ese VPS así que no hay ningún riesgo).

Abre tu terminal favorita. en mi caso yo uso WARP con ZSH. Pega el ssh que te dio Hostinger y presiona ENTER:

Te va a preguntar si estás seguro de querer conectarte y tu escribes yes. Al hacer esto te va a pedir la contraseña que estableciste al comienzo.

Verás que te da la bienvenida y hemos logrado conectarnos al VPS de Hostinger correctamente:

Lo primero que haremos al entrar

Vamos a actualizar nuestro sistema haciendo uso de los comandos:

sudo apt update && sudo apt upgrade
Bash

Configurando un usario nuevo

Dado que actualmente estamos haciendo use del usario root, lo cual es una mala practica y puede ser muy peligroso por muchas razones. Vamos a crear un nuevo usario que tenga privilegios de sudo. Para ello camos a escribir el comando:

adduser sofidev
Bash

Al presionar ENTER verás que kla terminal te solcita que agregues un nuevo password para este usuario. A continuación te pedira que lo repitas para confirmar y luego te pedirá algunos datos personales, los cuales puedes omitir presionando ENTER

Agregando nuestro usario a la lista de sudoers

Ahora que hemos creado nuestro usario, debemos agregarlo a la lista de sudoers. Con esto le estamos dando permiso a nuestro nuevo usario de ejecutar comandos con permisos de administrador, sudo. Vamos entonces a escibir el siguiente comando:

usermod -aG sudo <tu_usario>
#en mi caso es usermod -aG sudo sofidev 
Bash

Ahora cambiemos de usario, para ello simplemente usaremos el comando:

su - <tu_usario>
#en mi caso su -  sofidev
Bash

Instalando Docker en nuestro VPS

Para poder instala Docker vamos a hacer uso del siguiente comando:

sudo apt install docker.io docker-compose-v2 -y
Bash

Una vez instalada debemos reiniciar nuestro SSH para que los cambios surtan efecto.Para ello puedes usar el comando:

sudo reboot
Bash

Verás que te desconecta de tu VPS por lo que hay que iniciar de nuevo pero ene sta ocación no copiaremos tal cual el SSH de hostinger, ya que este nos inicia como root, sino que vamos a cambiar la palabra root por nuestro usario. En mic aso sofidev. te pedirá la contraseña que le pusiste a ese usario:

ssh sofidev@2.25.165.190 #En tu caso será tu ip que te asignó hostinger
Bash

¿Ya tienes tu app lista?

Llegó el momento de preparar nuestra app para deployment. Si no tienes una y quieres seguir los pasos de este tutorial, aquí te dejo el repositrorio del proyecto que hicimos. Cabe aclarar que ya viene completamente configurado y sin errores por lo que básicamente solo tendrás que seguir los pasos por puro amor al conocimiento.

Paso 1: La Orquestación con Docker

Utilizamos Docker Compose para definir y ejecutar nuestra aplicación multicontenedor. Esto garantiza que el entorno sea idéntico tanto en desarrollo local como en el servidor de Hostinger.

Crea un archivo docker-compose.yml en la raíz de tu proyecto.

services:
  # database
  db:
    image: mysql:8.0
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
    ports:
      - "3306:3306"
    volumes:
      - db_data:/var/lib/mysql

  # backend
  backend:
    image: node:20-alpine
    working_dir: /app
    volumes:
      - ./backend:/app
    ports:
      - "3000:3000"
    command: sh -c "npm install && npm run dev"
    environment:
      - DATABASE_URL=${DATABASE_URL}
    depends_on:
      - db

  # frontend
  frontend:
    image: node:20-alpine
    working_dir: /app
    volumes:
      - ./frontend:/app
    ports:
      - "5173:5173"
    command: sh -c "npm install && npm run dev -- --host"
    environment:
      - VITE_API_URL=${VITE_API_URL:-http://localhost:3000}
    depends_on:
      - backend

volumes:
  db_data:
YAML

Variables de Entorno

Nunca subas contraseñas a tu repositorio de código. Crea un archivo .env en el mismo directorio raíz y luego agregalo a tu archivo gitignore

MYSQL_ROOT_PASSWORD=TuContraseñaSegura123
MYSQL_DATABASE=midatabase
DATABASE_URL=mysql://root:TuContraseñaSegura123@db:3306/midatabase
Plaintext

Paso 2: Ajustes del Frontend para Producción

Al acceder a una aplicación de Vite a través de un nombre de dominio en lugar de localhost, Vite bloqueará la petición por motivos de seguridad. Necesitamos permitir explícitamente nuestro dominio.

Actualiza tu archivo frontend/vite.config.js:

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import tailwindcss from '@tailwindcss/vite';

export default defineConfig({
  plugins: [
    react(),
    tailwindcss(), //Dependencias usadas en este proyecto específico
  ],
  server: {
    host: true,
    port: 5173,
    allowedHosts: [
      'todo.sofidev.site' //Remplaza con tu dominio real,
    ],
  },
});
JavaScript

Actualiza las llamadas a la API en tu código de React para que apunten a la ruta segura que configuraremos en Nginx:

// Ejemplo en frontend/src/api.js
const API_URL = 'https://todo.midominio.com/api'; 
JavaScript

Para aplicar estos cambios, construye y levanta tus contenedores:

sudo docker compose up -d --build
Bash

Paso 3: Proxy Inverso con Nginx y Seguridad SSL

Nginx actuará como el recepcionista del servidor, recibiendo el tráfico en los puertos 80 y 443, y redirigiéndolo a los contenedores de Docker correspondientes para evitar problemas de «Contenido Mixto» (Mixed Content).

Instala Nginx:

sudo apt update
sudo apt install nginx -y
Bash

Crea un nuevo bloque de configuración para tu sitio:

sudo nano /etc/nginx/sites-available/todo.midominio.com
Bash

Pega la siguiente configuración:

server {
    listen 80;
    server_name todo.sofidev.site;

    location / {
        proxy_pass http://localhost:5173;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }

    # Esta es la línea que agregamos más adelante en el video tutorial:
    location /api/ {
        proxy_pass http://localhost:3000/;
    }
}
Nginx

Activa el sitio y recarga Nginx:

sudo ln -s /etc/nginx/sites-available/todo.midominio.com /etc/nginx/sites-enabled/
sudo systemctl reload nginx
Bash

Asegura la conexión con Let’s Encrypt (Certbot):

sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d todo.midominio.com
Bash

Así debería lucir la configuración final de nginx en tu server vps:

server {
    server_name todo.sofidev.site;

    location / {
        proxy_pass http://localhost:5173;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }
    #Esta es la línea que agregamos más adelante en el video tutorial:
    location /api/ {
     proxy_pass http://localhost:3000/;
    }
    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/todo.sofidev.site/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/todo.sofidev.site/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}
server {
    if ($host = todo.sofidev.site) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    listen 80;
    server_name todo.sofidev.site;
    return 404; # managed by Certbot
}
Nginx

Paso 4: Automatización de Despliegues con GitHub Actions

Entrar al servidor por SSH cada vez que hay un cambio en el código no es escalable. Gracias a la flexibilidad de nuestro VPS en Hostinger, podemos automatizar esto mediante un pipeline CI/CD.

Primero, añade las credenciales de tu servidor en tu repositorio de GitHub, navegando a Settings > Secrets and variables > Actions:

  • VPS_HOST: La dirección IP pública de tu servidor de Hostinger.
  • VPS_USERNAME: El usuario de tu servidor.
  • VPS_PASSWORD: La contraseña de tu servidor.

Luego, crea el siguiente archivo en tu repositorio: .github/workflows/deploy.yml:

YAML

name: CI/CD - Deploy Todo List

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Login in to VPS
        uses: appleboy/ssh-action@v1.0.0
        env:
          VPS_PASSWORD: ${{ secrets.VPS_PASSWORD }}
        with:
          host: ${{ secrets.VPS_HOST }}
          username: ${{ secrets.VPS_USERNAME }}
          password: ${{ secrets.VPS_PASSWORD }}
          envs: VPS_PASSWORD
          script: |
            cd ~/todo-list
            git pull origin main
            echo "$VPS_PASSWORD" | sudo -S docker compose up -d --build
YAML

Con esta configuración, cada comando git push a la rama main activará una conexión SSH segura a tu VPS, descargará el código más reciente y reconstruirá los contenedores de Docker de manera automática.

Conclusión

Has transformado un servidor en blanco en una infraestructura moderna, segura y completamente automatizada. Esta es exactamente la arquitectura que se utiliza en entornos profesionales y, gracias al excelente rendimiento de los VPS de Hostinger, tienes espacio de sobra para escalar este proyecto o alojar aplicaciones adicionales.

Si este tutorial te fue útil, no olvides aplicar tu [20% de descuento en Hostinger ingresando aquí]. Para más contenido sobre desarrollo con JavaScript, TypeScript y DevOps, asegúrate de seguirme en mi canal de YouTube SofiDev.