Files
Proxmox-Coolify-Manager/docs/casos/coolify-500-envvar-texto-plano.md
T

5.3 KiB

Caso: error 500 al abrir la página de una aplicación en Coolify (env var sin cifrar)

Fecha: 2026-09-04 · App: open-seo:main-0fgs5kwaab9esytaxkddsvts (uuid kj0kccsb4d46tm0d6qe6docy, id DB 57, repo every-app/open-seo, build pack railpack) · Resuelto el mismo día.

Síntoma

Abrir https://coolify.urieljareth.org/project/.../application/kj0kccsb4d46tm0d6qe6docy devuelve 500 (Server Error). El resto del dashboard funciona. El sitio público de la app responde 200 (lo sirve un sidecar manual, ver "Estado post-fix").

Causa raíz

La fila 1298 de environment_variables (DATAFORSEO_API_KEY de la app 57) tenía el valor en texto plano (56 chars, sin prefijo eyJpdiI6) con is_literal=false. En Coolify v4.3.x el accessor value del modelo EnvironmentVariable descifra incondicionalmente (decrypt($value)); un valor no cifrado lanza DecryptException: The payload is invalid.

La página de configuración monta ConfigurationChecker (Livewire), que llama a Application->pendingDeploymentConfigurationDiff() → ApplicationConfigurationSnapshot::environmentItems() → lee ->value de cada env var → explota → la página entera responde 500. El stack trace está en storage/logs/laravel.log del contenedor coolify.

El valor llegó por un INSERT/UPDATE directo a la DB (sin pasar por el modelo Eloquent, que cifra en el set). Huella correlativa: 5 filas más con el morph type mal escapado (App\\Models\\Application, doble backslash), también inserciones directas por SQL (ver "Hallazgos secundarios").

Diagnóstico (réplicable)

# 1) Stack trace del 500 (dentro del contenedor coolify):
#    docker exec coolify tail -n 200 /var/www/html/storage/logs/laravel.log
#    -> DecryptException desde EnvironmentVariable::get_environment_variables

# 2) Clasificar filas SIN imprimir valores (todo payload cifrado de Laravel
#    empieza con "eyJpdiI6"):
pct exec 102 -- docker exec coolify-db sh -c 'psql -U "$POSTGRES_USER" \
  -d "$POSTGRES_DB" -c "SELECT id, key, is_literal, length(value) AS len, \
  (value LIKE $$eyJpdiI6%$$) AS looks_enc FROM environment_variables \
  WHERE resourceable_id=57;"'

Fix aplicado

Re-cifrar el valor existente con el APP_KEY de la instancia (preserva el secreto; no hace falta reingresarlo), vía un script PHP con Laravel booteado dentro del contenedor coolify:

// /tmp/fix-envvar.php (se pasa por stdin a: docker exec -i coolify sh -c 'cat > /tmp/fix-envvar.php')
require '/var/www/html/vendor/autoload.php';
$app = require '/var/www/html/bootstrap/app.php';
$app->make(\Illuminate\Contracts\Console\Kernel::class)->bootstrap();
use Illuminate\Support\Facades\DB;

$row = DB::table('environment_variables')->where('id', 1298)->first();
try { decrypt($row->value); echo "ya cifra OK\n"; }
catch (\Throwable $e) {
    DB::table('environment_variables')->where('id', 1298)->update([
        'value' => encrypt($row->value),   // el secreto no se pierde
        'updated_at' => now(),
    ]);
}

Wrapper ejecutable: artifacts/fix-envvar-1298.ps1 (hace el backup, aplica y verifica en una pasada).

Backup previo (incluye el valor, root-only, host Proxmox): /root/backups/envvar-1298-20260904-211001.tsv.

Rollback: restaurar la fila desde el backup (UPDATE environment_variables SET value='<col 3 del tsv>' WHERE id=1298;) — solo si se quisiera volver al estado roto original; no hay razón para hacerlo.

Verificación

  • Lectura a nivel de modelo OK (el accessor ya no lanza).
  • Application::find(57)->pendingDeploymentConfigurationDiff() — la ruta exacta que 500eaba — ejecuta limpio.
  • Fila post-fix: len=312, looks_enc=t, updated_at=2026-09-05 03:10:04.

Estado post-fix de la app (no parte de este caso)

  • La app en Coolify sigue exited:unhealthy sin contenedor (última online 2026-08-27). Su página ya carga; un redeploy es decisión del usuario.
  • El FQDN https://kj0kccsb4d46tm0d6qe6docy.urieljareth.org responde 200 en vivo (cf-cache-status: DYNAMIC) porque el contenedor manual open-seo-sidecar (puerto 80, corriendo fuera de Coolify) lleva los labels Traefik de ese host. Es decir: el sitio público no depende hoy del deployment de Coolify.

Hallazgos secundarios (sin acción, reportados al usuario)

  • 5 filas huérfanas (ids 1152-1156: MYSQL_DATABASE, MYSQL_USER, MOSTRAR_ENLACE, SEMBRAR_SIEMPRE, ENLACES_POR_VENTANA) apuntan a la app 52 (insta-portal) con resourceable_type='App\\Models\\Application' (doble backslash). La relación de Eloquent no las ve, así que no rompen páginas, pero insta-portal corre sin esas variables. Normalizar el morph type (y re-cifrar valores) las activaría — evaluar impacto en runtime antes.

Prevención

  • Nunca escribir en environment_variables.value por SQL directo: el modelo cifra en el setter. Para insertar variables usar la UI o la API.
  • Si se inserta por SQL de emergencia, el valor debe ser encrypt($valor) con el APP_KEY de la instancia, y resourceable_type lleva un solo backslash (App\Models\Application).
  • Síntoma distintivo para el futuro: dashboard 500 solo en la página de una app concreta + DecryptException en laravel.log = valor corrupto en environment_variables de ese recurso.