Skip to content
docuconf
docuconf on GitHub

Example apps

Every SDK repository has a runnable example: the same small service, orders, built with that language's usual tools. Most are HTTP services; the COBOL one is a batch job, run as a Kubernetes CronJob. Comparing them shows how each SDK fits its host library while producing the same kind of contract.

Each example:

  • declares its configuration with the SDK, in the host library's own style;
  • shows its typed values with the secret redacted: the HTTP services on GET /config, beside GET /healthz, and the batch job in its output;
  • commits the contract.cue it exports, and CI checks that it is up to date and passes cue vet;
  • fails at startup with every problem listed, each with an error code, when the configuration is wrong.

The configuration

The configuration every example app declares
VariableTypeRules
PORTint1–65535, default 8080
LOG_LEVELenumdebug, info, warn, error; default info
DATABASE_URLurlsecret, required, scheme postgres
ALLOWED_ORIGINSlist of stringsat least 1 item; default http://localhost:3000
REQUEST_TIMEOUTduration1s–5m, default 30s
WORKER_COUNTint1–64, default 4

Environment variable names follow each host's conventions. Most examples read PORT; .NET reads ORDERS__PORT and Java (Spring Boot) reads ORDERS_PORT, from an Orders options section. The Laravel example reads ORDERS_LOG_LEVEL, because Laravel itself reads LOG_LEVEL. Each contract lists the exact names.

Side by side

Pick two SDKs and compare the declaration, the contract it exports, or what the app prints when PORT=0 and DATABASE_URL is missing. The code is copied from each repository's main branch, and CI fails when a copy goes stale.

Show
Go · internal/config/config.go
// Package config is the orders service's configuration: an ordinary
// caarlos0/env struct with docuconf's tags. It lives in its own package
// because `docuconf export` imports it, and package main cannot be imported.
package config

import (
	"time"

	"github.com/docuconf/docuconf-go"
)

// Config is everything the orders service reads at boot.
// Doc comments become the descriptions in the contract: the first
// paragraph is the description, and any later paragraphs are its details.
type Config struct {
	// HTTP listen port.
	Port int `env:"PORT" envDefault:"8080" min:"1" max:"65535"`

	// Minimum log level emitted.
	LogLevel string `env:"LOG_LEVEL" envDefault:"info" values:"debug,info,warn,error"`

	// Postgres connection string for the orders database.
	DatabaseURL docuconf.Secret `env:"DATABASE_URL,required" schemes:"postgres" maxLength:"2048"`

	// Origins allowed to call the API from a browser.
	AllowedOrigins []string `env:"ALLOWED_ORIGINS" envDefault:"http://localhost:3000" minItems:"1"`

	// Time limit for handling one request.
	RequestTimeout time.Duration `env:"REQUEST_TIMEOUT" envDefault:"30s" min:"1s" max:"5m"`

	// Number of background workers processing orders.
	//
	// Each worker holds one database connection, so keep it below the
	// database's connection limit divided by the number of replicas.
	// Raise it when the order queue grows faster than it drains.
	WorkerCount int `env:"WORKER_COUNT" envDefault:"4" min:"1" max:"64"`

	// Certificate to serve HTTPS with. Without it, the service serves HTTP.
	TLS docuconf.TLSKeyPair `file:"serving-tls" path:"/etc/orders/tls" dnsNames:"orders.example.com" minRemaining:"720h" reload:"watch"`

	// Discount codes accepted at checkout.
	Discounts docuconf.ConfigFile[Discounts] `file:"discounts" path:"/etc/orders/discounts/discounts.yaml"`
}

// Discounts is the content of the discounts file.
type Discounts struct {
	// Percent off for each discount code.
	Codes map[string]int `json:"codes" yaml:"codes"`
}
TypeScript (T3 Env) · src/env.ts
// The service's configuration: a T3 Env declaration with docuconf's helpers
// for what Zod has no word for (secrets, URL schemes, durations, lists).
import { z } from "zod";
import { createEnv, duration, list, secret, url } from "@docuconf/t3";

export const env = createEnv({
  name: "orders",
  server: {
    PORT: z.coerce.number().int().min(1).max(65535).default(8080).describe("Port the HTTP server listens on"),
    LOG_LEVEL: z.enum(["debug", "info", "warn", "error"]).default("info").describe("Minimum log level emitted"),
    DATABASE_URL: secret(url({ schemes: ["postgres"], maxLength: 2048 })).describe("Postgres connection string for the orders database"),
    ALLOWED_ORIGINS: list(z.string(), { minItems: 1 })
      .default(["http://localhost:3000"])
      .describe("Comma-separated CORS origins allowed to call the API"),
    REQUEST_TIMEOUT: duration({ min: "1s", max: "5m", default: "30s" }).describe("Timeout for a single request"),
    /**
     * Number of background order workers.
     *
     * Each worker holds one database connection, so keep this below the
     * pool size of {@link DATABASE_URL}'s server.
     *
     * - Raise it when the order queue backs up.
     * - Lower it when the database is the bottleneck.
     */
    WORKER_COUNT: z.coerce.number().int().min(1).max(64).default(4).describe("Number of background order workers"),
  },
  runtimeEnv: process.env,
  // On invalid configuration: print every problem and exit 1.
  exitOnError: true,
});

The examples

To deploy one, the platform validates its contract.cue before the pod starts, with docuconf vet and docuconf render or the Helm library chart. See How it works.