← Blog

Mudar de Render a tu propio servidor

Railyard

24 de abril de 2026 · 12 min de lectura

Sigue esto en tu propio servidor. Empieza gratis, sin tarjeta

Los precios de Render suben rápido en cuanto un servicio web necesita más que la instancia más pequeña, y su Postgres administrado agrega su propia línea aparte. Algunos equipos chocan con el comportamiento de suspensión del plan gratuito y aun así no quieren pagar un plan más grande solo para mantener despierto un proyecto secundario. Otros simplemente quieren un servidor que controlan, donde ninguna plataforma decide qué tamaños de instancia existen ni qué pasa si una tarjeta registrada vence. Esta guía mueve un servicio web de Render, su worker en segundo plano si tiene uno, y su base de datos Postgres administrada a un servidor que es tuyo. Al final tendrás la misma app corriendo desde su propio Dockerfile en un servidor que aprovisionaste tú, su base de datos restaurada con las mismas filas y el mismo esquema, HTTPS emitido automáticamente, y una tarea de respaldo que sigue guardando copias de los datos de ahí en adelante.

Lo que vas a necesitar

  • Un servidor nuevo. Usaremos un Hetzner CX23 (2 vCPU, 4 GB de RAM, 40 GB NVMe), listado en 5.99 € al mes sin IVA. Es suficiente margen para correr el contenedor de la app, un proceso worker si tienes uno, Postgres y un proxy inverso Caddy en la misma máquina, lo que cubre lo que un servicio web típico de Render más su base de datos necesitan. Si prefieres comparar proveedores primero, Railyard mantiene una guía de costos de hospedaje con cifras actuales de varios de ellos.
  • Un dominio que puedas apuntar al nuevo servidor. Un cambio de registro A tarda unos minutos en propagarse, y HTTPS no se emitirá hasta que eso ocurra.
  • Una clave de API de Render. Créala desde Account Settings en el panel de Render (dashboard.render.com/u/settings), luego “Add API Key”. Render emite claves con acceso a toda tu cuenta en lugar de una opción restringida de solo lectura, así que guárdala como guardarías cualquier otra credencial y bórrala cuando termines la migración.
  • El repositorio de tu app, con el Dockerfile que Render ya usaba para construirla. Si Render construía tu servicio con un runtime que no fuera Docker, vas a necesitar agregar un Dockerfile ahora, ya que nada en el nuevo servidor construye tu app por ti de otra forma.

Railyard también mantiene una guía de migración dedicada a Render con una comparación función por función, por si quieres ver qué cambia antes de empezar a mover nada.

Paso 1: obtener tu configuración actual desde la API de Render

Antes de tocar el nuevo servidor, consigue un registro exacto de lo que Render está corriendo en realidad, en lugar de confiar en la memoria o en un README viejo. La API de Render te permite leer todo lo que configura tu servicio.

export RENDER_API_KEY="rnd_your_key_here"

curl -s -H "Authorization: Bearer $RENDER_API_KEY" \
  "https://api.render.com/v1/services?limit=100" \
  | jq '.[].service | {id, name, type, repo}'

Eso lista todos los servicios de tu cuenta con su id. Elige el que vas a mover y obtén su detalle completo, variables de entorno y dominios personalizados:

SERVICE_ID="srv-xxxxxxxxxxxxxxxxxxxx"

curl -s -H "Authorization: Bearer $RENDER_API_KEY" \
  "https://api.render.com/v1/services/$SERVICE_ID" \
  | jq '{name, type, repo, branch, startCommand: .serviceDetails.envSpecificDetails.startCommand, ports: .serviceDetails.openPorts}'

curl -s -H "Authorization: Bearer $RENDER_API_KEY" \
  "https://api.render.com/v1/services/$SERVICE_ID/env-vars?limit=100" \
  | jq '.[].envVar'

curl -s -H "Authorization: Bearer $RENDER_API_KEY" \
  "https://api.render.com/v1/services/$SERVICE_ID/custom-domains?limit=100" \
  | jq '.[].customDomain.name'

Deberías ver la URL del repositorio de tu servicio, su rama, su comando de inicio y su puerto; la lista completa de variables de entorno, incluyendo DATABASE_URL si usas el Postgres administrado de Render; y los nombres de dominio personalizados asociados al servicio. Anota todo, vas a necesitar cada dato en los pasos siguientes.

Algo que esta API nunca te va a dar: el contenido de los archivos secretos asociados al servicio. Render no tiene ningún endpoint para eso, así que abre cada uno ahora en el panel y copia su contenido a mano antes de perder el acceso a la cuenta.

Paso 2: aprovisionar el nuevo servidor

Crea el servidor (un Hetzner CX23 o equivalente, con Ubuntu), agrega tu llave SSH al crearlo, y anota su IP pública. Luego conéctate y actualiza los paquetes.

ssh root@<server-ip>
apt-get update && apt-get upgrade -y

Deberías ver que el prompt regresa sin actualizaciones pendientes.

Paso 3: instalar Docker

curl -fsSL https://get.docker.com | sh

Comprueba que funcionó:

docker --version
docker compose version

Ambos comandos deberían mostrar un número de versión. El script de instalación de Docker incluye Compose como plugin, así que no hay un paso de instalación aparte para eso.

Paso 4: llevar tu repositorio y tu Dockerfile al servidor

git clone <your-repo-url> /opt/app
cd /opt/app

Si Render ya construía este servicio a partir de un Dockerfile, ya lo tienes listo. Si usaba un runtime tipo buildpack (Ruby, Node, y así), agrega un Dockerfile al repositorio ahora, ya que ese buildpack no existe fuera de Render.

Crea un archivo de entorno junto al repositorio con los valores que obtuviste en el paso 1, más una contraseña para la nueva base de datos:

# /opt/app/.env
POSTGRES_PASSWORD=choose-a-strong-password-here

Cualquier otra variable que Render te haya listado va en este mismo archivo, y se referencia desde docker-compose.yml en los pasos siguientes.

Paso 5: sacar la base de datos de Render

Abre la página Info de la base de datos en el panel de Render y busca la cadena de conexión externa que se muestra ahí (funciona desde fuera de la red de Render, a diferencia de la interna que usan los servicios dentro de Render para conectarse entre sí). Úsala para hacer un dump:

pg_dump --format=custom --no-owner --no-acl \
  "postgresql://user:password@dpg-xxxxxxxx.oregon-postgres.render.com/dbname" \
  -f render_dump.pgsql

Usa un pg_dump local que coincida con la versión mayor de Postgres que vas a correr en el nuevo servidor (16, para que coincida con la imagen del paso siguiente). Deberías ver aparecer un archivo render_dump.pgsql, con un tamaño más o menos proporcional a tus datos. Confirma que se puede leer antes de seguir:

pg_restore --list render_dump.pgsql | head

Deberías ver la lista de tus tablas, secuencias e índices.

Paso 6: iniciar Postgres en el nuevo servidor y restaurar

Escribe el archivo de Compose para toda la pila desde ahora, para que el paso 6 solo necesite el servicio de base de datos corriendo:

# /opt/app/docker-compose.yml
services:
  app:
    build: .
    restart: unless-stopped
    environment:
      DATABASE_URL: postgres://app:${POSTGRES_PASSWORD}@db:5432/app_production
      RAILS_ENV: production
    depends_on:
      - db
    expose:
      - "3000"

  worker:
    build: .
    command: bundle exec sidekiq
    restart: unless-stopped
    environment:
      DATABASE_URL: postgres://app:${POSTGRES_PASSWORD}@db:5432/app_production
      RAILS_ENV: production
    depends_on:
      - db

  db:
    image: postgres:16.4-alpine
    restart: unless-stopped
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: app_production
    volumes:
      - pgdata:/var/lib/postgresql/data

  caddy:
    image: caddy:2
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
    depends_on:
      - app

volumes:
  pgdata:
  caddy_data:

Quita el servicio worker si Render no corría uno para esta app. Levanta solo la base de datos por ahora:

docker compose up -d db

Copia el dump adentro y restáuralo:

docker cp render_dump.pgsql $(docker compose ps -q db):/tmp/render_dump.pgsql
docker compose exec db pg_restore --no-owner --no-acl \
  -U app -d app_production /tmp/render_dump.pgsql

Comprueba que llegó bien:

docker compose exec db psql -U app -d app_production -c '\dt'

Deberías ver los mismos nombres de tablas que viste con pg_restore --list en el paso 5.

Paso 7: levantar el resto de la pila

docker compose up -d --build

Comprueba:

docker compose ps

Deberías ver app, worker (si existe), db y caddy listados como en ejecución.

Paso 8: apuntar el DNS y dejar que Caddy emita HTTPS

Agrega un registro A para tu dominio apuntando a la IP del servidor. Escribe el Caddyfile:

# /opt/app/Caddyfile
app.example.com {
    reverse_proxy app:3000
}

Reemplaza app.example.com por cada dominio que listaste en el paso 1, un bloque por dominio si tienes más de uno. Reinicia Caddy para que tome el archivo:

docker compose restart caddy

Deberías ver un certificado válido emitirse en pocos segundos después de que el registro DNS resuelva: curl -I https://app.example.com devuelve un HTTP/2 200 sin advertencia de certificado.

Paso 9: configurar los respaldos

Agrega una tarea cron diaria en el servidor que haga un dump de la base de datos y mantenga una ventana de copias:

mkdir -p /opt/backups
crontab -e
0 3 * * * docker compose -f /opt/app/docker-compose.yml exec -T db \
  pg_dump -U app app_production | gzip > /opt/backups/app_$(date +\%F).sql.gz \
  && find /opt/backups -name '*.sql.gz' -mtime +14 -delete

Corre el comando a mano una vez para confirmarlo antes de esperar a las 3am:

docker compose exec -T db pg_dump -U app app_production | gzip > /opt/backups/app_test.sql.gz
ls -lh /opt/backups

Deberías ver un archivo .sql.gz con un tamaño distinto de cero.

Paso 10: hacer el cambio, con Render todavía corriendo como respaldo

Mantén el servicio de Render y su base de datos intactos durante todos los pasos anteriores. Antes de mover el DNS de verdad, confirma que el nuevo servidor sirve la app correctamente con una solicitud directa:

curl -H "Host: app.example.com" http://<server-ip>

Una vez que eso, un inicio de sesión, y un camino de escritura queden validados contra la base de datos restaurada, mueve el DNS del dominio al nuevo servidor (si no lo habías hecho ya en el paso 8) y dale tiempo para propagarse por completo. Solo después de confirmar que el tráfico está llegando al nuevo servidor deberías pausar o eliminar el servicio de Render.

Paso 11: mantener la app actualizada

cd /opt/app
git pull
docker compose up -d --build

Compose solo reconstruye y reinicia los servicios cuya imagen realmente cambió, así que un cambio pequeño de código no tumba la base de datos ni Caddy. Para una app que no tolera ni unos segundos de reinicio, necesitarías en su lugar un segundo contenedor de app y un cambio con balanceador de carga, lo cual va más allá de lo que cubre esta configuración de un solo servidor.

El atajo: hacerlo con Railyard

Todo lo anterior (leer la configuración de un servicio de Render a través de su API, obtener una cadena de conexión de base de datos externa, hacer un dump y restaurarlo) es exactamente lo que Applications → Import de Railyard hace por ti. Apúntalo a un token de API de Render y lee tus apps, muestra un plan por cada app antes de que confirmes nada, y crea la app en el servidor que elijas. Para un servicio de Render en particular, trae el repositorio y la rama, las variables de entorno, el comando de inicio y los dominios personalizados; un worker en segundo plano pasa a ser un proceso worker sin URL web, la misma distinción que harías a mano en el paso 6. La API de Render sigue sin exponerle a nadie el contenido de los archivos secretos, Railyard incluido, así que esa es la única pieza que agregas tú mismo después del import, igual que en el paso 1 de arriba. El import desde Render está actualmente en beta, así que lee la pantalla del plan antes de confirmar, de la misma forma en que revisarías de nuevo los valores del paso 1.

El lado de la base de datos también está disponible por sí solo, fuera de un import completo de la app. Import data de Railyard acepta una URL de base de datos origen en vivo o un archivo de dump subido, siempre respalda primero la base de datos de destino para que un import fallido o cancelado restaure automáticamente, y puede correr las migraciones de tu app justo después si eliges “deploy after import”. Cubre dumps de hasta 5 GiB por defecto.

Todos los detalles están en Import from another platform y Databases and backups. El plan gratuito de Railyard cubre una mudanza de este tamaño sin tarjeta: un servidor más un servidor de base de datos y tres apps.

Solución de problemas

Una llamada curl contra la API de Render devuelve 401 o 403. Las claves de API de Render no tienen alcances restringidos, no hay una opción de solo lectura o limitada, así que una solicitud rechazada casi siempre significa que la clave se escribió mal, se revocó, o se regeneró después de que la copiaste, no un permiso faltante. Genera una nueva desde Account Settings y vuelve a intentar.

El worker en segundo plano no arranca, o empieza a escuchar en un puerto que no debería. Un worker en segundo plano en Render nunca tuvo una URL pública desde el principio, así que no le pongas a su servicio de Compose una línea expose ni un bloque en el Caddyfile. Si en cambio se cae, revisa que su comando de inicio sea el del worker (bundle exec sidekiq, un consumidor de cola, etc.), no el del servicio web copiado por error.

La app arranca pero falla de inmediato en el puerto en el que escucha. El comando de inicio que copiaste en el paso 1 estaba ajustado para el entorno de Render, donde Render inyecta $PORT por su cuenta. Tu nuevo contenedor no va a fijar esa variable a menos que tú lo hagas, así que define PORT explícitamente en el bloque environment de Compose para que coincida con lo que expones, o modifica el comando de inicio para que se enlace a un puerto fijo.

Caddy nunca emite un certificado. Corre docker compose logs caddy y busca fallos de autorización repetidos o tiempos de espera agotados. Eso casi siempre significa que el registro A del dominio todavía no resuelve a este servidor. Compara dig +short app.example.com con la IP del propio servidor (curl -4 icanhazip.com corrido en el servidor) y espera a que coincidan antes de esperar que HTTPS funcione.

La app falla al arrancar quejándose de un archivo faltante. Si la ruta en el error coincide con donde Render guardaba un archivo secreto para este servicio, eso es lo único que la API de Render nunca te entrega. Abre el archivo en el panel de Render, copia su contenido, y recréalo como un archivo que tu contenedor monte o lea, o convierte su contenido en una variable de entorno si la app puede leerlo de esa forma en su lugar.

Preguntas frecuentes

¿Esto causa tiempo de inactividad? No para la base de datos: el Postgres de ambas plataformas se mantiene activo y legible hasta el paso 10, ya que estás leyendo de la copia de Render y escribiendo en la tuya. La app en sí tiene una ventana corta entre que el cambio de DNS se propaga y el nuevo servidor queda totalmente activo sirviendo los mismos datos, típicamente muy por debajo de un minuto si ya confirmaste la solicitud directa por IP en el paso 10.

¿Qué pasa con el comportamiento de suspensión del plan gratuito de Render? Eso es propio de los servicios web gratuitos de Render, que se duermen después de un período de inactividad y tardan unos segundos en despertar en la siguiente solicitud. Un contenedor en tu propio servidor corre de forma continua una vez que arranca, no hay un estado de suspensión equivalente que considerar.

¿Puedo mantener Render como respaldo durante la mudanza? Sí, para eso está pensado el paso 10: deja el servicio de Render y su base de datos corriendo sin tocarlos hasta que hayas confirmado que los inicios de sesión, las escrituras y cualquier tarea en segundo plano funcionan correctamente contra el nuevo servidor, luego mueve el DNS y solo pausa o cancela Render después.

¿Necesito recrear mis dominios personalizados yo mismo? Ya tienes la lista completa desde el paso 1. Apunta el DNS de cada dominio al nuevo servidor y agrega un bloque correspondiente en el Caddyfile para cada uno; Caddy solicita y renueva HTTPS para cada dominio de la misma forma.

¿El import de Railyard trae los archivos secretos automáticamente? No. Esa es la única pieza que ni el camino manual ni el import pueden automatizar, ya que la API de Render no le expone el contenido de los archivos secretos a nada fuera de Render, Railyard incluido. Los agregas tú mismo, a mano, una vez que el import termina, igual que lo harías en el paso 1.

Dejar Render no significa renunciar al Postgres administrado ni al HTTPS automático. Significa correr las mismas piezas (Docker Compose, Caddy, Postgres) tú mismo, en un servidor cuyo precio fijaste antes de siquiera registrarte en él. Ya sea que sigas los pasos de arriba a mano o dejes que Applications → Import se encargue de copiar el repositorio, las variables y la base de datos, terminas con una app que puedes ver, a la que puedes entrar por SSH, y que puedes respaldar sin pedirle permiso primero a un panel.

Por qué eligen Railyard para esto

  • Tu propia cuenta en la nube. Despliega en DigitalOcean, Hetzner, Vultr, AWS, OVHcloud, o cualquier servidor Linux por SSH, facturado directamente por tu proveedor, sin márgenes adicionales.
  • Un precio fijo por equipo. El plan Free es gratis para siempre: un servidor más un servidor de base de datos, tres apps, sin tarjeta. Pro cuesta $19 al mes por hasta cinco servidores y apps ilimitadas. Team cuesta $49 al mes con servidores ilimitados. Sin cargos por app, por despliegue, por asiento ni por gigabyte.
  • Plantillas de un clic. Despliega apps de código abierto, como bases de datos, analítica y herramientas de desarrollo, en tu propio servidor con un clic.
  • Bases de datos administradas. Postgres, MySQL y Redis, con copias de seguridad programadas y verificadas por restauración, y recuperación a un punto en el tiempo.
  • Despliegue con git push. Despliega desde GitHub, GitLab o la CLI, con HTTPS automático y lanzamientos sin tiempo de inactividad.

Sáltate los pasos manuales

Applications → Import de Railyard lee el repositorio, las variables y la base de datos de tu app de Render directamente desde un token de API y crea la app en el servidor que elijas.

Plan gratis, sin tarjeta, y 14 días de Pro para cada equipo nuevo. Un precio fijo por equipo.