Skip to content

Custom Resolvers Examples

Example 1: JSON File Resolver

A resolver that reads values from a local JSON file using dot-notation keys.

resolvers/json-file.js

const { readFileSync } = require('fs');
const { resolve } = require('path');

module.exports = class JsonFile extends Function {
    constructor(parsed) {
        super();
        const filePath = parsed.custom?.secrets_file || './secrets.json';
        const data = JSON.parse(readFileSync(resolve(filePath), 'utf-8'));

        return function jsonfile(key, fallback) {
            const parts = key.split('.');
            let value = data;
            for (const p of parts) {
                value = value?.[p];
                if (value === undefined) return fallback ?? null;
            }
            return value;
        };
    }
};

secrets.json

{
  "database": {
    "host": "db.internal.local",
    "port": "5432",
    "password": "s3cret"
  },
  "redis": {
    "url": "redis://cache.internal.local:6379"
  }
}

pctl.yaml

name: my-app

resolver:
  - ./resolvers/json-file.js

custom:
  secrets_file: ./secrets.json

services:
  api:
    image: ./Dockerfile
    env:
      DB_HOST: "${jsonfile:database.host}"
      DB_PORT: "${jsonfile:database.port}"
      DB_PASS: "${jsonfile:database.password}"
      REDIS_URL: "${jsonfile:redis.url}"
    scale:
      replica: 1
    ports:
      - 3000
    provider:
      name: docker

After resolution, the env block becomes:

env:
  DB_HOST: "db.internal.local"
  DB_PORT: "5432"
  DB_PASS: "s3cret"
  REDIS_URL: "redis://cache.internal.local:6379"

Example 2: HTTP API Resolver

A resolver that fetches values from an HTTP config service.

resolvers/http-config.js

const https = require('https');

module.exports = class HttpConfig extends Function {
    constructor(parsed) {
        super();
        const baseUrl = parsed.custom?.config_api || 'https://config.internal.local';
        const cache = new Map();

        function fetch(url) {
            return new Promise((resolve, reject) => {
                https.get(url, res => {
                    let data = '';
                    res.on('data', chunk => data += chunk);
                    res.on('end', () => {
                        try { resolve(JSON.parse(data)); }
                        catch { resolve(data); }
                    });
                    res.on('error', reject);
                }).on('error', reject);
            });
        }

        return function httpconfig(key, fallback) {
            if (cache.has(key)) return cache.get(key) ?? fallback ?? null;
            return fetch(`${baseUrl}/config/${key}`)
                .then(data => {
                    const value = data?.value ?? null;
                    cache.set(key, value);
                    return value ?? fallback ?? null;
                })
                .catch(() => {
                    cache.set(key, null);
                    return fallback ?? null;
                });
        };
    }
};

pctl.yaml

name: my-app

resolver:
  - ./resolvers/http-config.js

custom:
  config_api: "https://config.internal.local"

services:
  api:
    image: ./Dockerfile
    env:
      DB_URL: "${httpconfig:prod/db-url}"
      API_KEY: "${httpconfig:prod/api-key, default-key}"
      FEATURE_FLAGS: "${httpconfig:prod/features, {}}"
    scale:
      replica: 1
    ports:
      - 3000
    provider:
      name: docker

The resolver calls GET https://config.internal.local/config/prod/db-url and expects a JSON response like {"value": "postgresql://..."}. Results are cached per key.


Combining Custom Resolvers with Built-in

Custom resolvers work alongside built-in resolvers. You can nest them:

resolver:
  - ./resolvers/json-file.js

custom:
  secrets_file: "${env:SECRETS_PATH, ./secrets.json}"

services:
  api:
    image: ./Dockerfile
    env:
      # env resolver runs first (innermost), then jsonfile
      DB_HOST: "${jsonfile:${env:DB_KEY, database.host}}"
      # SSM fallback to json file
      API_KEY: "${ssm:/prod/api-key, ${jsonfile:api.key, fallback-key}}"
    scale:
      replica: 1
    provider:
      name: docker