← index

vps-coolify-setup

vps-coolify-setup.md

VPS Coolify Bootstrap — Setup de VPS nuevo para alojar apps

Skill de bootstrap paso a paso para armar un VPS Oracle Cloud Always Free desde cero, dedicado 100% a alojar apps web vía Coolify (PaaS self-hosted) con Traefik. Excluye juegos, Minecraft y similares — eso va por otra vía (containers manuales en /srv/games/).

Pensado para producción seria con clientes: Traefik + Let's Encrypt + Cloudflare + Umami + monitoreo a Telegram + backups automáticos.


🎯 Qué terminás teniendo al final del setup

  • VM ARM64 en Oracle Cloud Always Free (4 OCPU + 24 GB RAM, al tope del Free Tier)
  • Acceso SSH solo vía Tailscale (puerto 22 cerrado en OCI)
  • Coolify 4.x corriendo con Traefik incorporado
  • DNS en Cloudflare con proxy inteligente (gris para emitir cert LE, después naranja opcional)
  • HTTPS automático en cada app que deployes
  • Analytics con Umami self-hosted
  • Monitoreo edge-triggered a Telegram cada 5 min
  • Backups diarios automáticos del boot volume
  • Parches de seguridad automáticos
  • Headers de seguridad via Cloudflare Transform Rules (score A en securityheaders.com)
  • Renovate configurado en repos para alertas de seguridad de deps

0. Pre-requisitos (cuentas y herramientas)

Antes de tocar nada, tené listas estas cuentas:

Cuenta Uso
Oracle Cloud (siempre Free Tier, no upgradable a paid por accidente) Hostea la VM
Tailscale (cuenta personal, Free Tier 100 devices) Red privada para SSH
Cloudflare (cuenta + dominio agregado) DNS + proxy + headers de seguridad
GitHub Repos de las apps + GitHub App para Coolify
Telegram (bot + chat) Alertas de monitoreo
Resend / SendGrid / similar Email transaccional (SMTP bloqueado en OCI Always Free)

En tu máquina local (macOS Apple Silicon):

  • Tailscale.app instalado y logueado
  • SSH (~/.ssh/config con alias para el VPS)
  • OCI CLI (brew install oci-cli)
  • Python 3 (para decodificar JWT del token OCI)

1. Crear la VM en Oracle Cloud (Always Free)

1.1 Lanzar la instancia

OCI Console → Compute → Instances → Create instance

Campo Valor
Name vps (o el que quieras)
Placement AD cualquiera de tu home region
Image Ubuntu 20.04 (o 22.04) Minimal aarch64
Shape VM.Standard.A1.Flex (ARM Ampere)
OCPU 4 (máximo Free)
Memory (GB) 24 (máximo Free)
Boot volume 150 GB (default, dentro del Free)
VCN Crear nueva o usar default
Subnet Pública (con Internet Gateway)
Assign public IPv4
SSH key Generar nuevo par de claves o subir pública

⚠️ Si te da error "Out of capacity", reintentar cada 5-10 min. Es el problema #1 de Always Free. Si pasa 24h sin poder, probar otra AD de la misma región.

1.2 Asignar IP pública reservada (recomendado)

Para que la IP no cambie en stop/start:

  1. Networking → IPs → Reserve public IP (regional)
  2. Asignar a la VM recién creada
  3. Anotar el OCID de la IP

Sobrevive stop/start, gratis mientras esté assigned. Si la dejas unattached, empieza a cobrar.

1.3 Anotar OCIDs críticos (los vas a usar mil veces)

# En OCI CLI desde tu Mac, después de instalar y auth:
export INST_OCID="ocid1.instance.oc1.<region>.<...>"
export SEC_LIST_OCID="ocid1.securitylist.oc1.<region>.<...>"
export COMP_OCID="ocid1.tenancy.oc1..<...>"

Setup completo de OCI CLI: ver sección "Auth OCI" más abajo.


2. Reglas OCI Security List (puertos del host)

Estas reglas son PERMANENTES en OCI. El UFW del VPS no sirve de nada si OCI no las permite.

Protocolo Puerto Source Propósito Recomendado
ICMP all 0.0.0.0/0 Ping diagnóstico ✅ Sí
ICMP all 10.0.0.0/16 Intra-VCN ✅ Sí
TCP 80 0.0.0.0/0 HTTP → 443, ACME http-01 ✅ Sí (necesario para Let's Encrypt)
TCP 443 0.0.0.0/0 HTTPS apps ✅ Sí
TCP 22 0.0.0.0/0 SSH NO (usar Tailscale)
TCP 8000 0.0.0.0/0 Coolify UI NO (solo Tailscale)
TCP 8080 0.0.0.0/0 Traefik dashboard NO (solo Tailscale)

SSH por Tailscale, no por OCI. Mantener 22 cerrado en OCI elimina la superficie de ataque más común (bots escaneando SSH).

Cómo aplicar las reglas

# Bajar las reglas actuales a JSON
oci network security-list get \
  --security-list-id "$SEC_LIST_OCID" \
  --query 'data."ingress-security-rules"' > /tmp/rules.json

# Editar /tmp/rules.json (agregar/quitar las que correspondan)
# Después subir:
oci network security-list update \
  --security-list-id "$SEC_LIST_OCID" \
  --ingress-security-rules "file:///tmp/rules.json" \
  --force

3. Primer SSH + hardening inicial

3.1 Conectar por primera vez (vía IP pública con la clave que generaste)

ssh -i ~/.ssh/oci-vps.key ubuntu@<IP_PUBLICA>

3.2 Hardening base

# Update + upgrade
sudo apt update && sudo apt upgrade -y

# Timezone Chile
sudo timedatectl set-timezone America/Santiago

# UFW (después de instalar Tailscale, sino te quedás afuera)
sudo apt install -y ufw
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# NO abrir 22 — solo Tailscale entra
sudo ufw enable

# Hostname
sudo hostnamectl set-hostname vps

3.3 Parches automáticos (security updates)

Configurar unattended-upgrades para auto-instalar parches de seguridad y reiniciar a las 03:00 hora Chile si hace falta.

sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades
# Elegir "Yes"

Editar /etc/apt/apt.conf.d/50unattended-upgrades para asegurar:

  • Unattended-Upgrade::Allowed-Origins incluye ${distro_id}:${distro_codename}-security
  • Unattended-Upgrade::Automatic-Reboot "true";
  • Unattended-Upgrade::Automatic-Reboot-Time "06:00"; (06:00 UTC = 03:00 CLT en winter, 02:00 CLT en summer — ajustar a gusto)

3.4 Configurar alias SSH en tu Mac

cat >> ~/.ssh/config <<'EOF'

Host vps
  HostName <IP_PUBLICA>
  User ubuntu
  IdentityFile ~/.ssh/oci-vps.key
EOF

CRÍTICO: este bloque Host vps debe ir ANTES de cualquier Host * en ~/.ssh/config. El bloque Host * puede desactivar PubkeyAuth para hosts no específicos.


4. Instalar Tailscale (SSH privado, cerrar 22 en OCI)

Tailscale crea una VPN mesh sin abrir puertos. Después de esto, cerrar 22 en OCI.

4.1 Instalar Tailscale en el VPS

curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up --ssh --hostname=vps

4.2 Aprobar el VPS en tu tailnet

Tailscale te va a dar una URL para aprobar. Abrirla, aceptar.

4.3 Instalar Tailscale en tu Mac

brew install --cask tailscale
# Abrir Tailscale.app, loguear con misma cuenta
# Después de logueado, debería aparecer tu Mac y el VPS en la lista

4.4 Actualizar SSH config para usar Tailscale

cat >> ~/.ssh/config <<'EOF'

Host vps
  HostName 100.x.y.z   # la IP Tailscale del VPS (ver con `tailscale status` en el VPS)
  User ubuntu
EOF

4.5 Validar que Tailscale SSH funciona

# Desde tu Mac:
ssh vps  # debe entrar sin pedir clave, vía Tailscale SSH

# Anotar la IP Tailscale del VPS:
ssh vps "tailscale ip -4"

4.6 CERRAR PUERTO 22 EN OCI (operación crítica)

Una vez validado que Tailscale SSH funciona, eliminar la regla TCP 22 de la Security List.

Mantener como fallback solo si se rompe Tailscale (ver sección "Emergencia SSH" abajo).


5. Instalar Docker

# Instalar Docker oficial
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker ubuntu
# Logout y login de nuevo para que tome el grupo

# Validar
docker --version
docker run hello-world

Nota sobre ARM64

El VPS es ARM64. Cosas a tener en cuenta:

  • Imágenes oficiales (nginx, postgres, redis, node, python) son multi-arch → OK
  • Imágenes custom: verificar soporte ARM64 en Docker Hub (tag linux/arm64)
  • Si una imagen no soporta ARM64, compilar localmente desde Dockerfile

6. Instalar Coolify

Coolify es un PaaS self-hosted que wrappea Docker + Traefik + tu repo Git. Deployas apps con git push.

6.1 Install

ssh vps
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash

Tarda 3-5 min. Al final muestra URL + credenciales iniciales.

6.2 Primer acceso

Por Tailscale (puerto 8000):

http://<IP_TAILSCALE>:8000

Por HTTPS público con sslip.io (recomendado para acceder desde cualquier lado):

https://coolify.<IP_PUBLICA>.sslip.io

Coolify emite cert Let's Encrypt automático para *.sslip.io.

⚠️ Para que el cert Let's Encrypt emita OK, el subdominio coolify.*.sslip.io debe resolver a la IP pública. sslip.io lo hace por convención, no hace falta tocar DNS.

6.3 Setup inicial UI

  1. Cambiar password del admin (no dejar el random)
  2. Settings → Updates → habilitar auto-updates
  3. Settings → SSH Keys → agregar tu clave pública para git
  4. Keys & Tokens → API tokens → generar uno con permisos read+write allguardarlo en password manager

6.4 Obtener UUIDs clave de Coolify

Necesarios para API automation:

# Project "personal" UUID (ya existe, o crear uno nuevo)
# Server "localhost" UUID (ya existe - es el VPS mismo)
# En UI: click en Project / Server → URL tiene el UUID

PROJECT_UUID="<UUID del project 'personal'>"
SERVER_UUID="<UUID del server 'localhost'>"

6.5 Configurar GitHub App (para repos privados sin PAT)

  1. Settings → Integrations → GitHub App → Create
  2. Autorizar la App en tu cuenta GitHub (System Wide scope)
  3. Anotar el github_app_uuid que devuelve Coolify

Permite a Coolify clonar repos privados y recibir webhooks de push para auto-deploy.

6.6 Backup OBLIGATORIO del .env de Coolify

# En tu Mac:
scp vps:/data/coolify/source/.env ./coolify-env-backup-$(date +%F)

# Subir a password manager, borrar la copia local

Si perdés este .env, no podés desencriptar los secrets de las apps deployadas. Es el archivo más importante del VPS.


7. DNS en Cloudflare

7.1 Agregar el dominio a Cloudflare (si no está)

Si el dominio está en otro registrar, primero cambiar nameservers a los que Cloudflare te asigna. La propagación tarda 24-48h.

7.2 Configurar SSL/TLS

Cloudflare → SSL/TLS → Overview:

  • Mode: Full (strict)
  • Always Use HTTPS: ON
  • Automatic HTTPS Rewrites: ON
  • Minimum TLS Version: 1.2 (recomendado)

7.3 Apuntar el dominio a la IP del VPS

DNS → Records → Add:

Type: A
Name: @ (o subdominio)
IPv4: <IP_PUBLICA>
Proxy: Proxied (naranja) ← para producción

Recomendado agregar wildcard:

Type: A
Name: *
IPv4: <IP_PUBLICA>
Proxy: Proxied (naranja)

7.4 Regla de subdominios nuevos

SI vas a emitir cert Let's Encrypt para un subdominio nuevo, empezá con proxy en gris (DNS-only).

Cloudflare proxy reescribe headers HTTP y puede romper el challenge HTTP-01 de Let's Encrypt. Traefik se queda en loop intentando emitir el cert.

Procedimiento:

  1. Cloudflare → DNS → Records → Add record
  2. Type A, name mi-app, IPv4 <IP_PUBLICA>, proxy DNS only (gris)
  3. En Coolify configurar la app con FQDN https://mi-app.midominio.com
  4. Coolify emite cert Let's Encrypt automático
  5. Validar con curl -sI https://mi-app.midominio.com → cert válido
  6. Recién después cambiar a Proxied (naranja) en Cloudflare para DDoS protection

8. Headers de seguridad (Cloudflare Transform Rules)

SIEMPRE en Cloudflare, NUNCA en Traefik labels ni custom_labels de Coolify. Razones:

  • Aplica a TODAS las responses (200, 308, 404, 500)
  • Cero contacto con el VPS / Coolify / Traefik
  • Si rompe algo, 1 click en CF UI desactiva la rule
  • Reusable para múltiples dominios desde la misma cuenta

8.1 Setup

Cloudflare → DOMAIN → RulesTransform Rules → tab Modify Response HeaderCreate rule (template "Add static header to response"). Crear UNA rule por header.

8.2 Los 6 headers estándar

Rule name Header name Value
add-xframe X-Frame-Options DENY
add-xcontent X-Content-Type-Options nosniff
add-xss X-XSS-Protection 1; mode=block
add-referrer Referrer-Policy strict-origin-when-cross-origin
add-permissions Permissions-Policy camera=(), microphone=(), geolocation=()
add-hsts Strict-Transport-Security max-age=31536000; includeSubDomains

Para cada rule:

  • If incoming requests match = All incoming requests
  • Action = Add static
  • Deploy

8.3 Validar

curl -sI https://midominio.com | grep -iE 'x-frame|x-content|x-xss|referrer|permissions|strict-transport'
# Debería listar los 6

Score esperado: A en https://securityheaders.com/?q=midominio.com

8.4 NO incluir CSP por default

CSP (Content-Security-Policy) requiere whitelistear cada servicio externo (analytics, ads, pagos, Sentry, fonts). Sin whitelist completa, rompe la app (carga blanca, scripts bloqueados). Solo agregar si necesitás A+ para audit de seguridad B2B.


9. Analytics con Umami (self-hosted)

9.1 Deploy via Coolify

Coolify → Add Resource → One-Click Services → buscar Umami → Deploy.

Coolify te pide dominio. Usar un subdominio dedicado:

  • analytics.midominio.com

DNS en Cloudflare: agregar A record con DNS only (gris) al principio. Let's Encrypt necesita llegar directo al VPS. Después de emitido el cert, podés dejarlo gris (no necesita DDoS protection - solo recibe el script.js).

9.2 Post-deploy

  1. Coolify espera ~1-2 min, UMAMI listo
  2. Entrar a https://analytics.midominio.com con admin / umami (cambiar password INMEDIATAMENTE)
  3. Settings → Websites → Add → name + domain
  4. Copiar el snippet: <script defer src="https://analytics.midominio.com/script.js" data-website-id="..."></script>
  5. Pegar en el <head> de cada app que quieras trackear

10. Monitoreo a Telegram (script edge-triggered)

10.1 Crear bot de Telegram

  1. Hablar con @BotFather/newbot → obtener BOT_TOKEN
  2. Crear grupo o chat personal, agregar el bot
  3. Obtener CHAT_ID (con @userinfobot o via https://api.telegram.org/bot<TOKEN>/getUpdates)

10.2 Crear directorio de credenciales en el VPS

ssh vps
sudo mkdir -p /opt/vps-monitor
sudo tee /opt/vps-monitor/monitor.env > /dev/null <<'EOF'
BOT_TOKEN="<TU_BOT_TOKEN>"
CHAT_ID="<TU_CHAT_ID>"
EOF
sudo chmod 600 /opt/vps-monitor/monitor.env

10.3 Instalar el script de monitoring

Crear /opt/vps-monitor/check.sh con un script bash que:

  • Use flock para evitar corridas concurrentes
  • Reintente 3 veces los checks HTTP (12s timeout, 4s entre reintentos)
  • Separe capa local (Traefik interno, vía --resolve) de capa pública (vía Cloudflare, como usuario real)
  • Sea edge-triggered: solo notifica cuando un check cambia de OK↔FAIL
  • Persista estado en /var/lib/vps-monitor/state/<check>.state
  • Escriba heartbeat a /var/log/vps-monitor.log por corrida
  • Mande mensajes HTML a Telegram con timestamp + emojis 🔴/🟢

Checks sugeridos (editar para tu setup):

  • Disco > 85%, RAM > 90%, Load > 8 (2x cores)
  • Containers core: coolify, coolify-proxy, coolify-db, coolify-redis, + prefijo UUID de cada app
  • HTTP check de cada dominio público (vía Cloudflare, con reintentos)
  • HTTP check LOCAL de cada app (sin Cloudflare, para detectar problemas del VPS vs CDN)

10.4 Cron

sudo crontab -e
# Agregar:
*/5 * * * * /opt/vps-monitor/check.sh >> /var/log/vps-monitor.log 2>&1

10.5 UptimeRobot externo (complementario)

Script local puede fallar si el VPS entero está down. UptimeRobot es un check externo que te avisa aunque el VPS esté muerto.

  • https://uptimerobot.com (Free: 50 monitors, check cada 5 min, alerta a email)
  • Agregar un HTTP monitor por cada dominio público

11. Backups automáticos (Oracle CLI)

11.1 Crear backup policy custom (Always Free friendly)

⚠️ NO usar las policies Gold/Silver/Bronze default — pueden exceder el Free Tier de 5 backups.

Crear policy custom en OCI:

  1. Block Storage → Backup Policies → Create Custom Policy
  2. Daily backup schedule a la hora que quieras
  3. Retention: 4 días (= máximo 4 backups concurrentes, dentro del límite de 5)
  4. Tipo: Incremental

11.2 Asignar policy al boot volume

oci bv backup-policy-assignment create \
  --asset-id "$BOOT_VOL_OCID" \
  --policy-id "$POLICY_OCID" \
  --query 'data.id'

11.3 Validar que se ejecutan

oci bv boot-volume-backup list --compartment-id "$COMP_OCID" --output table

11.4 Backup adicional OFF-site (recomendado para producción con clientes)

Los backups OCI viven en la misma región. Si la región cae, perdés todo.

Opciones baratas:

  • Backblaze B2 (10 GB gratis)
  • Cloudflare R2 (10 GB gratis, sin egress)
  • rsync nocturno vía cron

12. SRE Checklist (operación continua)

12.1 Reglas duras para cambios en producción

  1. Snapshot antes de cualquier cambio (curl GET a la app, guardar en /tmp/backup-*.json)
  2. Validar HTTP 200 × 60s post-cambio. Si 5xx → revert INMEDIATO sin debuggear
  3. Cambios de routing/proxy/domains: probar en staging (sslip.io) primero
  4. Cambios riesgosos en horario bajo (madrugada Chile)
  5. Headers: SIEMPRE via Cloudflare, NUNCA via custom_labels Traefik
  6. NO mezclar custom_labels con auto-config de Coolify (Coolify genera sus propios labels, los tuyos pueden romper el YAML)
  7. Limpiar custom_labels requiere UPDATE applications SET custom_labels=NULL WHERE uuid='...' via SQL directo (la API rechaza string vacío)
  8. Post-deploy: confirmar curl -sI https://DOMAIN antes de declarar éxito (status "finished" ≠ serving)

12.2 Pre-flight checks antes de cualquier health check

# 1) OCI session token vigente (expira 1h, refresh hasta 24h)
oci session refresh --profile DEFAULT 2>&1 | tail -2

# 2) Tailscale up
tailscale status 2>&1 | grep -E "Tailscale is|active" | head -3

Sin ambos, perdés 30+ segundos en timeouts.

12.3 Límites Always Free a no olvidar

Recurso Límite
Compute ARM A1 4 OCPU + 24 GB RAM TOTAL
Compute AMD E2.1.Micro 2 instancias (1/8 OCPU + 1 GB c/u)
Block Storage TOTAL 200 GB
Boot Volume Backups 5 TOTALES
VCN 2
Reserved Public IPs 1 gratis si assigned
Outbound bandwidth 10 TB/mes
TCP 25 (SMTP) BLOQUEADO (usar Resend/SendGrid)

Si Oracle reclama la VM por "idle" (>7 días sin uso real), restaurar desde el último backup en una nueva instancia.

12.4 Renovate en cada repo (alertas de seguridad de deps)

  1. Instalar https://github.com/apps/renovate (1 click por repo)
  2. Agregar renovate.json al repo:
{
  "extends": ["config:recommended"],
  "minimumReleaseAge": "7 days",
  "vulnerabilityAlerts": { "enabled": true, "minimumReleaseAge": "0 days" },
  "schedule": ["before 8am on monday"]
}

13. Agregar una app nueva (playbook)

13.1 DNS primero (si va a tener dominio propio)

Cloudflare → DNS → Add record:

Type: A, Name: mi-app, IPv4: <IP_PUBLICA>, Proxy: DNS only (gris)

13.2 Crear app en Coolify via API

COOLIFY_API="http://100.x.y.z:8000/api/v1"  # o https://coolify.<IP>.sslip.io/api/v1
COOLIFY_TOKEN="<tu token>"

curl -X POST -H "Authorization: Bearer $COOLIFY_TOKEN" -H "Content-Type: application/json" \
  -d '{
    "project_uuid": "'"$PROJECT_UUID"'",
    "server_uuid": "'"$SERVER_UUID"'",
    "environment_name": "production",
    "github_app_uuid": "'"$GITHUB_APP_UUID"'",
    "git_repository": "usuario/mi-app",
    "git_branch": "main",
    "build_pack": "nixpacks",
    "ports_exposes": "3000",
    "name": "mi-app",
    "domains": "https://mi-app.midominio.com,https://mi-app-staging.<IP>.sslip.io",
    "instant_deploy": false
  }' \
  "$COOLIFY_API/applications/private-github-app"

13.3 Setear env vars + deploy + validar

Ver playbook completo en vps-personal skill (Steps 4-8). Lo crítico:

  • App de staging (sslip.io) primero, prod después
  • Validar con curl -sI https://mi-app-staging.<IP>.sslip.io → 200 con cert válido
  • Después agregar el dominio prod a la config de Coolify
  • Actualizar env var ORIGIN al dominio prod
  • Trigger redeploy
  • Una vez validado en prod, opcionalmente pasar el subdominio a Cloudflare Proxied

13.4 Agregar al monitoring

  • UptimeRobot: nuevo monitor para el dominio
  • /opt/vps-monitor/check.sh: agregar check HTTP con reintentos
  • Umami: Settings → Websites → Add → pegar snippet en <head> de la app

14. Troubleshooting

"ssh vps" timeout

tailscale status                    # ver si ambos nodos están "active"
ssh vps-public                      # fallback (requiere reabrir 22 en OCI)

"Permission denied (publickey)" en ssh vps

El bloque Host * al final de ~/.ssh/config puede desactivar PubkeyAuth para hosts no específicos. El bloque Host vps debe ir antes de Host *.

Docker: "no matching manifest for linux/arm64"

La imagen es solo amd64. Opciones:

  • Buscar alternativa con linux/arm64
  • docker pull --platform linux/arm64 imagen:tag
  • Compilar localmente desde Dockerfile

Disco lleno

ssh vps "df -h /; docker system df"
ssh vps "docker system prune -af --volumes"   # ⚠️ borra containers parados, imágenes sin usar, volúmenes huérfanos

VPS parece lento / se cae periódicamente

ssh vps "uptime; free -h; iostat 1 5"
ssh vps "journalctl --since '1 hour ago' | grep -iE 'oom|kill|error'"

Normal: reboot a las 3 AM Chile si un parche lo requiere.

Tailscale dejó de funcionar tras reboot del VPS

sudo systemctl restart tailscaled
sudo tailscale up --ssh --hostname=vps

Cert Let's Encrypt no emite

docker logs coolify-proxy --tail 100 | grep -iE 'error|acme|letsencrypt'

Causas comunes:

  • Subdominio en Cloudflare con Proxy ON → cambiar a DNS-only
  • Puerto 80 cerrado en OCI Security List → abrirlo
  • DNS no resuelve a la IP del VPS → validar con dig mi-app.midominio.com

App responde 503 tras agregar dominio nuevo

PATCH /api/v1/applications/{uuid} con {"domains": "https://nuevo.dominio,..."} y trigger redeploy. Traefik no tiene regla para ese Host header.

App deployó "finished" pero no responde

# Validar que el container está Up
ssh vps "docker ps --filter name=<app>"

# Si está Up, validar local
ssh vps "curl -sI http://localhost:<port>"

# Si local responde pero externo no → problema de proxy/DNS/firewall

15. Emergencia SSH (Tailscale roto)

Si Tailscale queda en loop y no podés entrar:

  1. Consola OCI → Networking → Security Lists → agregar TCP 22 desde 0.0.0.0/0 temporalmente
  2. ssh vps-public (vía IP pública con la clave RSA)
  3. Diagnosticar y reparar Tailscale:
    sudo systemctl restart tailscaled
    sudo tailscale up --ssh --hostname=vps
    
  4. Validar que tailscale status muestra tu Mac y el VPS
  5. Validar ssh vps desde tu Mac sin pedir clave
  6. Cerrar puerto 22 en OCI de nuevo

16. Consideraciones para producción con clientes

El VPS puede alojar apps con clientes pagando, con caveats:

OK para:

  • Side projects, MVPs, lanzamientos beta
  • Apps de baja-media demanda (< ~100 usuarios concurrentes)
  • Servicios internos con clientes B2B sin SLA estricto
  • Demos, staging, herramientas de equipo

NO recomendado para:

  • Apps con SLA contractual (Oracle no garantiza uptime en Always Free)
  • Apps que NO toleren reboot automático a las 3 AM
  • Datos críticos sin backups externos (los snapshots OCI están en la misma región)
  • Apps con tráfico alto y picos (1 instancia, 1 región, 1 AD = SPOF)

Recomendaciones cuando vayas a producción:

  1. Cloudflare proxy ON sobre el dominio (DDoS + WAF + oculta IP del VPS)
  2. HTTPS automático vía Coolify Traefik + Let's Encrypt
  3. Backup adicional OFF-site (rsync nocturno a R2/B2, ver sección 11.4)
  4. Monitoreo externo (UptimeRobot gratis 50 monitors)
  5. Sentry o equivalente para errores de runtime
  6. Backup del /data/coolify/source/.env a password manager (ver sección 6.6)
  7. Renovate activado en cada repo
  8. Si crecen los clientes, considerar tier pagado de OCI o migrar a infra con redundancia

17. Anexo: Auth OCI CLI (para gestión de infra desde tu Mac)

Install

brew install oci-cli

Auth inicial

oci session authenticate --region sa-valparaiso-1 --profile-name DEFAULT
# → abre browser a Oracle SSO
# → loguear con la cuenta del VPS
# → el browser redirige a http://localhost:8181 (puede dar "sitio no alcanzable", IGNORAR — el token ya se guardó)

Bug conocido: "Key user | missing"

OCI CLI 3.84.0 (brew) NO escribe la línea user= al ~/.oci/config. Fix:

USER_OCID=$(python3 -c "
import json, base64
with open('$HOME/.oci/sessions/DEFAULT/token') as f:
    parts = f.read().strip().split('.')
payload = parts[1] + '=' * (4 - len(parts[1]) % 4)
print(json.loads(base64.urlsafe_b64decode(payload))['sub'])
")

cp ~/.oci/config ~/.oci/config.bak
sed -i '' "/^tenancy=/a\\
user=${USER_OCID}
" ~/.oci/config
cat ~/.oci/config  # debe tener user=... después de tenancy=

Env var permanente

cat >> ~/.zshenv <<'EOF'

export OCI_CLI_AUTH=security_token
export INST_OCID="ocid1.instance.oc1.<region>.<...>"
export SEC_LIST_OCID="ocid1.securitylist.oc1.<region>.<...>"
export COMP_OCID="ocid1.tenancy.oc1..<...>"
EOF
source ~/.zshenv

Sin OCI_CLI_AUTH=security_token, todos los comandos fallan con WARNING: security_token_file which is not being used + 401.

Renovar sesión cuando expira

# Renovar (hasta 24h)
oci session refresh --profile DEFAULT

# Si refresh falla (token >24h viejo):
oci session authenticate --region sa-valparaiso-1 --profile-name DEFAULT
# → después REPETIR el fix del user OCID

Comandos clave

# Estado instancia
oci compute instance get --instance-id "$INST_OCID" \
  --query 'data.{State:"lifecycle-state",OCPUs:"shape-config".ocpus,RAM:"shape-config"."memory-in-gbs"}'

# Stop / Start
oci compute instance action --instance-id "$INST_OCID" --action STOP --wait-for-state STOPPED
oci compute instance action --instance-id "$INST_OCID" --action START --wait-for-state RUNNING

# Resize (STOPPED)
echo '{"ocpus": 4.0, "memoryInGBs": 24.0}' > /tmp/shape.json
oci compute instance update --instance-id "$INST_OCID" --shape-config "file:///tmp/shape.json" --force

# Backups
oci bv boot-volume-backup list --compartment-id "$COMP_OCID" --output table

# Security List rules
oci network security-list get --security-list-id "$SEC_LIST_OCID" \
  --query 'data."ingress-security-rules"'

18. Resumen rápido — checklist de primer deploy

  • Cuenta Oracle Cloud Always Free + tenancy activa
  • VM.Standard.A1.Flex creada (4 OCPU, 24 GB, 150 GB disco, Ubuntu ARM64)
  • IP pública reservada asignada y anotada
  • OCIDs de instance, security list, compartment, boot volume guardados
  • Security List con reglas: ICMP, TCP 80, TCP 443 (sin 22)
  • SSH inicial via IP pública con clave RSA
  • UFW configurado (80, 443 permitidos)
  • Timezone America/Santiago
  • unattended-upgrades activo
  • Tailscale instalado en VPS + Mac, SSH validado
  • Puerto 22 CERRADO en OCI
  • Docker instalado y testeado
  • Coolify instalado, password cambiado, API token guardado
  • /data/coolify/source/.env respaldado en password manager
  • GitHub App configurada en Coolify
  • Dominio en Cloudflare, NS propagados, SSL/TLS = Full (strict)
  • A record del dominio al VPS (con wildcard * recomendado)
  • 6 Transform Rules de seguridad configuradas
  • Umami deployado y password cambiado
  • Script de monitoreo instalado, Telegram bot configurado, cron activo
  • UptimeRobot con monitor del primer dominio
  • Backup policy custom de OCI creada y asignada al boot volume
  • Renovate instalado en cada repo
  • SSH config en Mac con Host vps apuntando a IP Tailscale (no IP pública)
  • OCI CLI instalado y auth funcionando con OCI_CLI_AUTH=security_token
  • OCIDs exportados en ~/.zshenv

Cuando todo esté ✅, ya tenés un VPS production-ready para alojar apps vía Coolify, con SRE básico, Always Free compliant.


Próximo paso lógico: deployar la primera app siguiendo la sección 13.