immich-app/immich · error
Failed to decode redis options
Error message
Failed to decode redis options
What it means
REDIS_URL may carry ioredis options as a base64-encoded JSON blob prefixed with 'ioredis://'. When that suffix is present, getEnv decodes slice(10) from base64 and JSON.parses it; any decode/parse failure (invalid base64, or base64 that is not valid JSON) is wrapped in this error.
Solutions
- Regenerate the payload: node -e 'console.log("ioredis://"+Buffer.from(JSON.stringify({host:"redis"})).toString("base64"))' and use the output as REDIS_URL
- Verify the suffix decodes: node -e 'JSON.parse(Buffer.from(process.argv[1].slice(10),"base64"))' "$REDIS_URL"
- If you just want a plain connection, drop the ioredis:// scheme and set REDIS_URL=redis://redis:6379 (or REDIS_HOSTNAME etc.) instead
- Check the compose/env layer isn't mangling the value (no stray quotes, spaces, or line breaks)
Example fix
// before REDIS_URL=ioredis://redis:6379 // after REDIS_URL=redis://redis:6379 # or properly encoded: REDIS_URL=ioredis://eyJob3N0IjoicmVkaXMifQ==
Defensive patterns
Strategy: validation
Validate before calling
const url = process.env.REDIS_URL;
if (url?.startsWith('ioredis://')) {
try {
JSON.parse(Buffer.from(url.slice(10), 'base64').toString());
} catch (e) {
throw new Error(`REDIS_URL is not valid base64-encoded JSON: ${e}`);
}
} Type guard
const isEncodedRedisOptions = (v: string | undefined): boolean =>
!!v?.startsWith('ioredis://') && (() => { try { JSON.parse(Buffer.from(v.slice(10), 'base64').toString()); return true; } catch { return false; } })(); Try / catch
try { start(); } catch (e) { if ((e as Error).message === 'Failed to decode redis options') { console.error('REDIS_URL must be redis://... or ioredis:// + base64(JSON). Re-encode:', e.cause); process.exit(1); } throw e; } Prevention
- Prefer plain redis:// URLs unless you need advanced ioredis options
- Generate the encoded value with Buffer.from(JSON.stringify(opts)).toString('base64') — never hand-write it
- Ensure the env layer (compose/K8s) does not add quotes, spaces, or line wraps to the value
- Test-decode REDIS_URL in CI before deploying
When it happens
Trigger: REDIS_URL starts with 'ioredis://' but the remainder is not valid base64 (spaces, URL-encoding, truncated string) or decodes to bytes that are not valid JSON (e.g. plain 'redis://host' content pasted after the prefix).
Common situations: Users copying an example REDIS_URL and replacing the JSON payload but leaving it unencoded; YAML/compose interpolating quotes or newlines into the value; a sentinel/template string left unfinished; manually base64-encoding with padding stripped incorrectly or double-encoding.
Understand the failure class
Background: "is not a valid" / "Invalid ... value" environment variable errors: how libraries validate env vars and what to do when they reject yours — this error's family across 48 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Failed to read helmet file
- Invalid environment variables:
- Invalid telemetry found
- Invalid worker(s) found
- Cannot update configuration while IMMICH_CONFIG_FILE is in…
AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15).
Data as JSON: /api/errors/869a18eb23e7e619.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/repositories/config.repository.ts:212
geodata: join(buildFolder, 'geodata'),
web: join(buildFolder, 'www'),
};
let redisConfig = {
host: dto.REDIS_HOSTNAME || 'redis',
port: dto.REDIS_PORT || 6379,
db: dto.REDIS_DBINDEX || 0,
username: dto.REDIS_USERNAME || undefined,
password: dto.REDIS_PASSWORD || undefined,
path: dto.REDIS_SOCKET || undefined,
};
const redisUrl = dto.REDIS_URL;
if (redisUrl && redisUrl.startsWith('ioredis://')) {
try {
redisConfig = JSON.parse(Buffer.from(redisUrl.slice(10), 'base64').toString());
} catch (error) {
throw new Error('Failed to decode redis options', { cause: error });
}
}
const includedTelemetries =
dto.IMMICH_TELEMETRY_INCLUDE === 'all'
? new Set(Object.values(ImmichTelemetry))
: asSet<ImmichTelemetry>(dto.IMMICH_TELEMETRY_INCLUDE, []);
const excludedTelemetries = asSet<ImmichTelemetry>(dto.IMMICH_TELEMETRY_EXCLUDE, []);
const telemetries = setDifference(includedTelemetries, excludedTelemetries);
for (const telemetry of telemetries) {
if (!TELEMETRY_TYPES.has(telemetry)) {
throw new Error(`Invalid telemetry found: ${telemetry}`);
}
}
const databaseConnection: DatabaseConnectionParams = dto.DB_URL
? { connectionType: 'url', url: dto.DB_URL }View on GitHub (pinned to e55ac299a4)