Articulo

Oracle Free Tier desde cero hasta produccion (guia real completa)

Guia paso a paso, con rutas reales del panel Oracle y comandos exactos, para levantar web + API + CI/CD sin perderte en red, DNS, SSL y despliegue.

Este post es el playbook que me habria gustado tener al empezar: no solo teoria, sino secuencia exacta de pasos con nombres de pantallas y comandos reales.

La idea es que tus colegas puedan replicarlo de principio a fin: VM en Oracle, hardening, dominio, HTTPS, reverse proxy, deploy de apps y troubleshooting.

Hay cosas que no suelen salir en tutoriales cortos y aqui si: permisos de llave en Windows, apt lock al instalar certbot, diferencias docker compose v1/v2 y cache web rota por base-href.

Los datos sensibles estan anonimizados, pero la operativa y las decisiones tecnicas son las mismas que use en produccion.

1) Preflight: region, compartment y limites (antes de crear nada)

En Oracle Cloud Console, selecciona primero la region correcta y el compartment donde vas a trabajar.

Revisa limites reales de Free Tier para no bloquearte a mitad del proceso.

Ruta recomendada: Governance & Administration > Limits, Quotas and Usage. Filtra por servicio (Compute/Networking) y por compartment.

  • Si no ves limites, suele ser por region/compartment mal seleccionado.
  • No empieces a desplegar si todavia no sabes donde va a vivir la instancia final.

2) Crear instancia y llave SSH en Oracle

Ruta: Compute > Instances > Create instance. Usa imagen Ubuntu LTS y shape de Free Tier.

Genera o sube tu clave publica SSH durante la creacion. Guarda bien la privada porque sera tu acceso principal.

Cuando termine, valida que puedes entrar por SSH antes de tocar red, DNS o Nginx.

ssh -i oracle.key ubuntu@TU_DOMINIO_O_IP
hostnamectl

3) Reserved Public IP y asociacion al VNIC (paso critico)

No dejes IP ephemeral si vas a exponer servicios publicos: tarde o temprano te rompe DNS.

Ruta para crearla: Networking > Public IPs > Create Reserved Public IP.

Luego asociala al Primary Private IP del VNIC de la instancia.

  • Compute > Instances > TU_INSTANCIA > Attached VNICs
  • Selecciona Primary VNIC y asigna la Reserved Public IP

4) Abrir puertos en Oracle Cloud (Security List/NSG)

Si no abres 80/443 en Oracle, da igual lo que configures en el servidor: no llega trafico.

Ruta habitual: Networking > Virtual Cloud Networks > TU_VCN > Subnets > TU_SUBNET > Security Lists.

Crea ingress rules para 22, 80 y 443.

  • TCP 22 desde 0.0.0.0/0 (o restringido a tu IP si puedes).
  • TCP 80 desde 0.0.0.0/0.
  • TCP 443 desde 0.0.0.0/0.

5) Primer SSH en Windows: fix de permisos de la key

Caso real: OpenSSH en Windows puede rechazar tu key por ACL demasiado abierta.

Si te da error de permisos, sanea la ACL de la llave antes de seguir.

takeown /f oracle.key
icacls oracle.key /inheritance:r
icacls oracle.key /grant "PC\\TU_USUARIO:(F)"
icacls oracle.key /remove "BUILTIN\\Usuarios" "NT AUTHORITY\\Usuarios autentificados"
ssh -i oracle.key ubuntu@TU_DOMINIO_O_IP

6) Hardening base del host (usuario, SSH, UFW, fail2ban)

Antes de publicar servicios, deja el host decente: usuario de operacion, sudo, SSH endurecido, firewall y fail2ban.

Este bloque evita mucho ruido automatizado y errores operativos posteriores.

sudo apt update && sudo apt -y upgrade
sudo adduser --disabled-password --gecos "" juan
sudo usermod -aG sudo juan
sudo rsync --archive --chown=juan:juan /home/ubuntu/.ssh /home/juan

printf "juan ALL=(ALL) NOPASSWD:ALL\n" | sudo tee /etc/sudoers.d/juan >/dev/null
sudo chmod 440 /etc/sudoers.d/juan
sudo visudo -cf /etc/sudoers.d/juan

sudo tee /etc/ssh/sshd_config.d/99-hardening.conf >/dev/null <<'EOF'
PasswordAuthentication no
PermitRootLogin no
PubkeyAuthentication yes
EOF
sudo sshd -t && sudo systemctl restart ssh

sudo apt -y install ufw fail2ban
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw --force enable
sudo systemctl enable --now fail2ban

7) DNS del dominio (sin esto no hay HTTPS ni web estable)

En tu proveedor DNS, apunta raiz y www a la Reserved Public IP.

Si tienes parking o wildcard viejo, limpialo para no enmascarar el trafico real.

Si usas correo en el mismo dominio, no toques MX/TXT/SPF al ajustar web.

  • A @ -> IP_PUBLICA_RESERVADA
  • A www -> IP_PUBLICA_RESERVADA

8) Nginx con rutas por path: landing + app web + API

Esta estructura fue clave para convivir con varios proyectos en un solo host.

Ejemplo real: landing en /, app Flutter en /heroesytareas/ y API en /heroesytareas-api/.

server {
  server_name ejemplo.dev www.ejemplo.dev;
  root /var/www/landing;
  index index.html;

  location = /heroesytareas { return 301 /heroesytareas/; }
  location ^~ /heroesytareas/ {
    root /var/www;
    try_files $uri $uri/ /heroesytareas/index.html;
  }

  location = /heroesytareas-api { return 301 /heroesytareas-api/; }
  location ^~ /heroesytareas-api/ {
    proxy_pass http://127.0.0.1:8080;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
  }

  location / {
    try_files $uri $uri/ /index.html;
  }
}

9) HTTPS con certbot y el problema real del apt lock

Con DNS resuelto, instala certbot y emite cert para dominio y www.

Error real que me paso: apt bloqueado por apt-daily/unattended-upgrades en el peor momento.

Si te ocurre, desactiva temporalmente timers/servicios, instala certbot, y vuelve a activar.

sudo apt -y install certbot python3-certbot-nginx
sudo certbot --nginx -d ejemplo.dev -d www.ejemplo.dev --redirect

# si apt lock bloquea
sudo systemctl stop apt-daily.service apt-daily-upgrade.service unattended-upgrades || true
sudo systemctl stop apt-daily.timer apt-daily-upgrade.timer || true
# instalar certbot y luego reactivar timers
sudo systemctl start apt-daily.timer apt-daily-upgrade.timer || true

10) Runtime de apps: Docker para API, systemd para Next.js

Una decision que me funciono: API Java en Docker y web Next.js como servicio systemd.

Docker simplifica dependencias de backend; systemd da control rapido para la web y metricas del host.

# API (docker)
docker compose -f docker-compose.yml up -d --build --remove-orphans

# Web (systemd)
sudo tee /etc/systemd/system/d3v-es.service >/dev/null <<'EOF'
[Unit]
Description=d3v-es Next.js app
After=network.target

[Service]
Type=simple
User=juan
WorkingDirectory=/home/juan/apps/d3v-es
Environment=NODE_ENV=production
Environment=PORT=3000
Environment=HOSTNAME=127.0.0.1
ExecStart=/usr/bin/npm run start
Restart=always

[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now d3v-es

11) CI/CD por GitHub Actions (que realmente despliega)

Configura deploy por SSH para sincronizar codigo al host, construir y reiniciar servicios.

No dejes esto a medias: automatizar evita drift entre lo que crees que hay y lo que realmente corre.

  • Secrets minimos: SSH_HOST, SSH_USER, SSH_PORT, SSH_KEY, DEPLOY_PATH.
  • Para OAuth/admin: NEXTAUTH_URL, NEXTAUTH_SECRET, GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET.
  • Despues de cada deploy, valida por curl local en el host y por URL publica.

12) Compose v1 vs v2: bug real que rompe deploys

Me encontre errores de metadata de contenedores al usar docker-compose v1 con Docker reciente.

Solucion: usa Compose v2 (`docker compose`) siempre que puedas y deja fallback explicito en scripts.

docker compose version
docker compose -f docker-compose.yml up -d --build --remove-orphans

13) Cache y subpath en Flutter web: el bug de pantalla en blanco

Si sirves Flutter en subruta (/heroesytareas/), el build DEBE llevar base href correcto.

Error real: publicar con `<base href="/">` deja la app en blanco porque intenta cargar recursos desde raiz.

Ademas, define no-cache en index/manifest/version y cache largo en assets versionados.

  • Build correcto: `flutter build web --release --base-href /heroesytareas/ --pwa-strategy=none`
  • index.html + manifest + version.json -> no-cache
  • assets/js/canvaskit -> max-age=31536000, immutable

14) Checklist final de validacion (copiar y pegar)

Antes de compartir la URL con nadie, pasa este checklist completo.

Te ahorra el tipico `en mi maquina va` y detecta roturas de proxy/cert/cache al momento.

# host
systemctl status nginx --no-pager
systemctl status d3v-es --no-pager
docker ps
ss -tuln
sudo certbot certificates

# publico
curl -I https://ejemplo.dev/
curl -I https://ejemplo.dev/heroesytareas/
curl -I https://ejemplo.dev/heroesytareas-api/
curl -I https://ejemplo.dev/blog/

Si tuviera que resumirlo en una sola idea: no es un problema de instalar paquetes, sino de ordenar bien red, DNS, proxy, runtime, deploy y cache.

Con ese orden, Oracle Free Tier alcanza de sobra para proyectos personales serios y compartibles con otras personas sin que te explote en cuanto refrescan la pagina.

Si quieres, en el siguiente post saco una version 100% checkeable para equipos: plantillas de workflow, nginx y service listos para copiar.