glanceapp/glance · error

reading secret file: %v

Error message

reading secret file: %v

What it means

Thrown by parseConfigVariableOfType for {{ secret.NAME }} expressions when os.ReadFile of /run/secrets/NAME fails (missing file, permission denied, directory absent). Docker's secrets mechanism mounts files under /run/secrets; the value used is the trimmed file contents. The underlying fs error is wrapped with 'reading secret file:'.

Source

Thrown at internal/glance/config.go:207

// When the bool return value is true, it indicates that the caller should use the original value
func parseConfigVariableOfType(variableType, variableName string) (string, bool, error) {
	switch variableType {
	case configVarTypeEnv:
		if !envVariableNamePattern.MatchString(variableName) {
			return "", true, nil
		}

		v, found := os.LookupEnv(variableName)
		if !found {
			return "", false, fmt.Errorf("environment variable %s not found", variableName)
		}

		return v, false, nil
	case configVarTypeSecret:
		secretPath := filepath.Join("/run/secrets", variableName)
		secret, err := os.ReadFile(secretPath)
		if err != nil {
			return "", false, fmt.Errorf("reading secret file: %v", err)
		}

		return strings.TrimSpace(string(secret)), false, nil
	case configVarTypeFileFromEnv:
		if !envVariableNamePattern.MatchString(variableName) {
			return "", true, nil
		}

		filePath, found := os.LookupEnv(variableName)
		if !found {
			return "", false, fmt.Errorf("readFileFromEnv: environment variable %s not found", variableName)
		}

		if !filepath.IsAbs(filePath) {
			return "", false, fmt.Errorf("readFileFromEnv: file path %s is not absolute", filePath)
		}

		fileContents, err := os.ReadFile(filePath)

View on GitHub (pinned to 91324e8de7)

Solutions

  1. Ensure the file exists at exactly /run/secrets/NAME with the secret as its contents (Docker secrets or manual placement: mkdir -p /run/secrets && echo -n 'value' > /run/secrets/NAME).
  2. Verify the process user can read the file (chmod 644 / appropriate ownership).
  3. Check the name inside {{ secret.X }} matches the mounted secret's filename exactly.
  4. In compose, declare and map the secret: secrets: [db_password] under the service plus top-level secrets.
  5. Read the wrapped error — it tells you whether it was 'no such file' vs 'permission denied'.

Example fix

# docker-compose.yml (before)
services:
  glance:
    configs:
      - glance.yml   # secret never mounted

# after
services:
  glance:
    secrets:
      - db_password
secrets:
  db_password:
    file: ./db_password.txt
Defensive patterns

Strategy: validation

Validate before calling

# pre-flight check for every {{ secret.X }} in glance.yml
mkdir -p /run/secrets
for s in $(grep -oE '\{\{ *secret\.[A-Za-z0-9_-]+ *\}\}' glance.yml | sed -E 's/.*secret\.([A-Za-z0-9_-]+).*/\1/'); do
  [ -r "/run/secrets/$s" ] || { echo "secret not readable: /run/secrets/$s"; exit 1; }
done

Prevention

When it happens

Trigger: glance.yml contains {{ secret.db_password }} but /run/secrets/db_password does not exist on the Glance host, the process lacks read permission, or /run/secrets itself is absent (non-Docker or non-swarm deployment without manually placing the file).

Common situations: Running the Docker image without `--secret` / compose `secrets:` mapping; running the binary directly on a host where the secrets dir was never created; file owned by root while Glance runs as a non-root user; secret name typo.

Related errors


AI-assisted analysis of glanceapp/glance@91324e8de7 (2026-08-15). Data as JSON: /api/errors/9252c90cabcd63cc. Report an issue: GitHub.