Open source · spec draft v1alpha1
Your environment variables are an API. Give them a contract.
docuconf turns the config your app already declares, with the library you already use (caarlos0/env, T3 Env, NestJS config, the .NET Options pattern, pydantic-settings, anyway_config and 10 more), into a CUE contract that your Kubernetes platform checks before anything deploys.
docuconf vet checks the values a platform proposes against the app's contract.
$ cat values.yaml DATABASE_URL: "postgres://orders:hunter2@db:5432/orders" DATABSE_URL: "postgres://orders@db:5432/orders" PORT: 70000 $ docuconf vet -contract contract.cue -values values.yaml DATABASE_URL: is secret, so it must come from a secretKeyRef, written {secretKeyRef: {name: <secret>, key: <key>}}, or an injector, never a literal or another reference DATABSE_URL: is not declared in the contract (check the spelling) PORT: 70000 is above max 65535
The Go SDK checks the real environment when the app starts.
$ PORT=0 go run . docuconf: 2 configuration problems: PORT: 0 is below min 1 (out_of_range) DATABASE_URL: is required but not set (missing_required) exit status 1
The problem
Config is the API nobody wrote down.
Today
- Environment variables are undocumented, untyped strings.
- A typo in
DATABSE_URLis found by a crash loop, often in production. - Every service invents its own validation, in every language.
- The platform team guesses what each app needs from READMEs and Slack threads.
With docuconf
- Every variable has a type, constraints and a description, declared in code.
- A bad value is rejected before a pod starts, with a precise reason.
- One contract format, whatever the app is written in.
- Platform policy is layered on top, without editing the app.
How it works
Declared once. Checked before deploy, and again at boot.
- 1
Declare
Describe your variables in the env library you already use. docuconf adds descriptions, secrets and constraints.
- 2
Export
The SDK writes contract.cue from the same declaration. You commit it, or publish it with your image.
- 3
Validate
docuconf vet, the Helm chart or CUE in your pipeline checks the values for each environment, plus platform policy, before anything renders.
- 4
Boot
At startup the SDK checks the real environment, including secret contents, and hands your code typed values.
From your code to the platform
Keep your library. Get a contract.
The same service, orders, declared with each SDK, and the contract it exports. Pick your language; every snippet is checked against that SDK's main branch in CI.
1 · Your declaration, as you write it today
// 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"`
}2 · The contract your platform checks
// Code generated by docuconf. DO NOT EDIT.
package orders
import "docuconf.dev/contract"
contract.#Contract & {
apiVersion: "docuconf.dev/v1alpha1"
kind: "ConfigContract"
metadata: {
name: "orders-api"
generator: {
language: "go"
sdk: "docuconf-go"
version: "0.1.0"
}
}
vars: {
ALLOWED_ORIGINS: {
type: "list"
description: "Origins allowed to call the API from a browser"
default: ["http://localhost:3000"]
items: "string"
encoding: "csv"
separator: ","
minItems: 1
}
DATABASE_URL: {
type: "url"
description: "Postgres connection string for the orders database"
required: true
secret: true
schemes: ["postgres"]
maxLength: 2048
}
LOG_LEVEL: {
type: "enum"
description: "Minimum log level emitted"
default: "info"
values: ["debug", "info", "warn", "error"]
}
PORT: {
type: "int"
description: "HTTP listen port"
default: 8080
min: 1
max: 65535
}
REQUEST_TIMEOUT: {
type: "duration"
description: "Time limit for handling one request"
default: "30s"
min: "1s"
max: "5m"
encoding: "go"
}
WORKER_COUNT: {
type: "int"
description: "Number of background workers processing orders"
details: "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."
default: 4
min: 1
max: 64
}
}
files: {
discounts: {
type: "config"
format: "yaml"
description: "Discount codes accepted at checkout"
path: "/etc/orders/discounts/discounts.yaml"
schema: {
type: "object"
required: ["codes"]
additionalProperties: false
properties: {
codes: {
type: "object"
description: "Percent off for each discount code"
additionalProperties: {
type: "integer"
}
}
}
}
}
"serving-tls": {
type: "tls"
description: "Certificate to serve HTTPS with. Without it, the service serves HTTP"
secret: true
path: "/etc/orders/tls"
reload: "watch"
dnsNames: ["orders.example.com"]
minRemaining: "720h"
}
}
}1 · Your declaration, as you write it today
// 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,
});2 · The contract your platform checks
// Code generated by docuconf. DO NOT EDIT.
package orders
import "docuconf.dev/contract"
contract.#Contract & {
apiVersion: "docuconf.dev/v1alpha1"
kind: "ConfigContract"
metadata: {
name: "orders"
generator: {
language: "typescript"
sdk: "@docuconf/t3"
version: "0.1.0"
}
}
vars: {
ALLOWED_ORIGINS: {
type: "list"
description: "Comma-separated CORS origins allowed to call the API"
items: "string"
encoding: "csv"
separator: ","
minItems: 1
default: ["http://localhost:3000"]
}
DATABASE_URL: {
type: "url"
description: "Postgres connection string for the orders database"
required: true
secret: true
schemes: ["postgres"]
maxLength: 2048
}
LOG_LEVEL: {
type: "enum"
description: "Minimum log level emitted"
values: ["debug", "info", "warn", "error"]
default: "info"
}
PORT: {
type: "int"
description: "Port the HTTP server listens on"
min: 1
max: 65535
default: 8080
}
REQUEST_TIMEOUT: {
type: "duration"
description: "Timeout for a single request"
encoding: "go"
min: "1s"
max: "5m"
default: "30s"
}
WORKER_COUNT: {
type: "int"
description: "Number of background order workers"
details: "Each worker holds one database connection, so keep this below the\npool size of `DATABASE_URL`'s server.\n\n- Raise it when the order queue backs up.\n- Lower it when the database is the bottleneck."
min: 1
max: 64
default: 4
}
}
}1 · Your declaration, as you write it today
// The service's configuration: the class-validator class @nestjs/config
// validates the environment with, plus docuconf's decorators for what
// class-validator has no word for (descriptions, secrets, URL schemes,
// durations, lists).
import { ArrayMinSize, IsEnum, IsInt, IsString, Max, MaxLength, Min } from "class-validator";
import { Describe, Duration, List, Secret, UrlSchemes, docuconfValidate } from "@docuconf/nestjs";
export enum LogLevel {
Debug = "debug",
Info = "info",
Warn = "warn",
Error = "error",
}
export class OrdersConfig {
@IsInt() @Min(1) @Max(65535) @Describe("Port the HTTP server listens on")
PORT: number = 8080;
@IsEnum(LogLevel) @Describe("Minimum log level emitted")
LOG_LEVEL: LogLevel = LogLevel.Info;
@Secret() @UrlSchemes("postgres") @MaxLength(2048) @Describe("Postgres connection string for the orders database")
DATABASE_URL!: string;
@List() @IsString({ each: true }) @ArrayMinSize(1) @Describe("Comma-separated CORS origins allowed to call the API")
ALLOWED_ORIGINS: string[] = ["http://localhost:3000"];
@Duration({ min: "1s", max: "5m", default: "30s" }) @Describe("Timeout for a single request")
REQUEST_TIMEOUT!: number;
/**
* 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.
*/
@IsInt() @Min(1) @Max(64) @Describe("Number of background order workers")
WORKER_COUNT: number = 4;
}
// exitOnError: on invalid configuration, print every problem and exit 1.
export const validate = docuconfValidate(OrdersConfig, { name: "orders", exitOnError: true });2 · The contract your platform checks
// Code generated by docuconf. DO NOT EDIT.
package orders
import "docuconf.dev/contract"
contract.#Contract & {
apiVersion: "docuconf.dev/v1alpha1"
kind: "ConfigContract"
metadata: {
name: "orders"
generator: {
language: "typescript"
sdk: "@docuconf/nestjs"
version: "0.1.0"
}
}
vars: {
ALLOWED_ORIGINS: {
type: "list"
description: "Comma-separated CORS origins allowed to call the API"
items: "string"
encoding: "csv"
separator: ","
minItems: 1
default: ["http://localhost:3000"]
}
DATABASE_URL: {
type: "url"
description: "Postgres connection string for the orders database"
required: true
secret: true
schemes: ["postgres"]
maxLength: 2048
}
LOG_LEVEL: {
type: "enum"
description: "Minimum log level emitted"
values: ["debug", "info", "warn", "error"]
default: "info"
}
PORT: {
type: "int"
description: "Port the HTTP server listens on"
min: 1
max: 65535
default: 8080
}
REQUEST_TIMEOUT: {
type: "duration"
description: "Timeout for a single request"
encoding: "go"
min: "1s"
max: "5m"
default: "30s"
}
WORKER_COUNT: {
type: "int"
description: "Number of background order workers"
details: "Each worker holds one database connection, so keep this below the\npool size of `DATABASE_URL`'s server.\n\n- Raise it when the order queue backs up.\n- Lower it when the database is the bottleneck."
min: 1
max: 64
default: 4
}
}
}1 · Your declaration, as you write it today
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using Docuconf;
namespace Orders.Api;
// The Orders section: Orders:Port is the environment variable ORDERS__PORT, and so on.
// Initializers are the defaults the contract records.
[ConfigContract("orders-api", Section = "Orders")]
public sealed class OrdersOptions
{
[Range(1, 65535)]
[Description("HTTP listen port")]
public int Port { get; set; } = 8080;
[AllowedValues("debug", "info", "warn", "error")]
[Description("Minimum level of log messages to write")]
public string LogLevel { get; set; } = "info";
// [Secret]: the platform must supply it from a Kubernetes Secret, and docuconf never prints it.
// [MaxLength] bounds the URL in characters; a longer one fails startup with out_of_range.
[Required, Secret, UrlSchemes("postgres"), MaxLength(2048)]
[Description("Postgres connection string for the orders database")]
public string DatabaseUrl { get; set; } = "";
// A list arrives as ORDERS__ALLOWEDORIGINS__0, ORDERS__ALLOWEDORIGINS__1, ...
[MinLength(1)]
[Description("Origins allowed to call the API from a browser")]
public List<string> AllowedOrigins { get; set; } = ["http://localhost:3000"];
// A TimeSpan arrives as hh:mm:ss (00:00:30); the platform writes "30s" and renders it that way.
[Range(typeof(TimeSpan), "00:00:01", "00:05:00")]
[Description("Time allowed to handle one request")]
public TimeSpan RequestTimeout { get; set; } = TimeSpan.FromSeconds(30);
// An XML doc comment works instead of [Description]: the <summary> is the description, and the <remarks> are the
// details, longer docs for docuconf docs (the project sets GenerateDocumentationFile).
/// <summary>Background workers that process new orders.</summary>
/// <remarks>
/// <para>
/// Each worker holds one connection from the pool of <see cref="DatabaseUrl"/>, so keep this below the database's
/// connection limit.
/// </para>
/// <list type="bullet">
/// <item><description>Raise it when the order queue backs up.</description></item>
/// <item><description>Lower it when the database is the bottleneck.</description></item>
/// </list>
/// </remarks>
[Range(1, 64)]
public int WorkerCount { get; set; } = 4;
}2 · The contract your platform checks
// Code generated by docuconf. DO NOT EDIT.
package orders_api
import "docuconf.dev/contract"
contract.#Contract & {
apiVersion: "docuconf.dev/v1alpha1"
kind: "ConfigContract"
metadata: {
name: "orders-api"
generator: {language: "dotnet", sdk: "Docuconf.Options", version: "0.1.0-alpha.1"}
}
vars: {
ORDERS__ALLOWEDORIGINS: {
type: "list"
description: "Origins allowed to call the API from a browser"
configKey: "Orders:AllowedOrigins"
items: "string"
encoding: "indexed"
minItems: 1
default: ["http://localhost:3000"]
}
ORDERS__DATABASEURL: {
type: "url"
description: "Postgres connection string for the orders database"
required: true
secret: true
configKey: "Orders:DatabaseUrl"
schemes: ["postgres"]
maxLength: 2048
}
ORDERS__LOGLEVEL: {
type: "enum"
description: "Minimum level of log messages to write"
configKey: "Orders:LogLevel"
values: ["debug", "info", "warn", "error"]
default: "info"
}
ORDERS__PORT: {
type: "int"
description: "HTTP listen port"
configKey: "Orders:Port"
min: 1
max: 65535
default: 8080
}
ORDERS__REQUESTTIMEOUT: {
type: "duration"
description: "Time allowed to handle one request"
configKey: "Orders:RequestTimeout"
encoding: "timespan"
min: "1s"
max: "5m"
default: "30s"
}
ORDERS__WORKERCOUNT: {
type: "int"
description: "Background workers that process new orders"
details: "Each worker holds one connection from the pool of `OrdersOptions.DatabaseUrl`, so keep this below the database's connection limit.\n\n- Raise it when the order queue backs up.\n- Lower it when the database is the bottleneck."
configKey: "Orders:WorkerCount"
min: 1
max: 64
default: 4
}
}
}1 · Your declaration, as you write it today
class Settings(DocuconfSettings):
# metadata.name in the exported contract.
docuconf_service: ClassVar[str] = "orders"
port: int = Field(8080, ge=1, le=65535, description="HTTP listen port")
log_level: Literal["debug", "info", "warn", "error"] = Field("info", description="Minimum log level")
# SecretStr makes it a secret in the contract, and keeps it out of reprs and error messages.
database_url: Annotated[SecretStr, Url(schemes=("postgres",))] = Field(
max_length=2048, description="Postgres connection string for orders"
)
# NoDecode + Csv: read "a,b" rather than pydantic-settings' default JSON list.
allowed_origins: Annotated[list[str], NoDecode, Csv()] = Field(
["http://localhost:3000"], min_length=1, description="CORS origins allowed to call the API"
)
# pydantic reads durations as ISO 8601 (PT45S); the contract says so, and the platform converts "45s".
request_timeout: timedelta = Field(
timedelta(seconds=30),
ge=timedelta(seconds=1),
le=timedelta(minutes=5),
description="Timeout for a request to finish",
)
"""How long a request may take before the server gives up on it.
Raise it when clients upload large order batches. Keep it below the load balancer's idle timeout, or the
client sees a reset rather than a ``504``.
The platform writes Go durations such as ``45s``; docuconf converts them to ISO 8601 for pydantic.
"""
worker_count: int = Field(4, ge=1, le=64, description="Workers processing orders")2 · The contract your platform checks
// Code generated by docuconf. DO NOT EDIT.
package orders
import "docuconf.dev/contract"
contract.#Contract & {
apiVersion: "docuconf.dev/v1alpha1"
kind: "ConfigContract"
metadata: {
name: "orders"
generator: {
language: "python"
sdk: "docuconf-pydantic"
version: "0.1.0"
}
}
vars: {
ALLOWED_ORIGINS: {
type: "list"
description: "CORS origins allowed to call the API"
items: "string"
encoding: "csv"
separator: ","
minItems: 1
default: ["http://localhost:3000"]
}
DATABASE_URL: {
type: "url"
description: "Postgres connection string for orders"
required: true
secret: true
schemes: ["postgres"]
maxLength: 2048
}
LOG_LEVEL: {
type: "enum"
description: "Minimum log level"
values: ["debug", "info", "warn", "error"]
default: "info"
}
PORT: {
type: "int"
description: "HTTP listen port"
min: 1
max: 65535
default: 8080
}
REQUEST_TIMEOUT: {
type: "duration"
description: "Timeout for a request to finish"
details: "How long a request may take before the server gives up on it.\n\nRaise it when clients upload large order batches. Keep it below the load balancer's idle timeout, or the\nclient sees a reset rather than a `504`.\n\nThe platform writes Go durations such as `45s`; docuconf converts them to ISO 8601 for pydantic."
encoding: "iso8601"
min: "1s"
max: "5m"
default: "30s"
}
WORKER_COUNT: {
type: "int"
description: "Workers processing orders"
min: 1
max: 64
default: 4
}
}
}1 · Your declaration, as you write it today
# frozen_string_literal: true
require "docuconf/anyway"
class OrdersConfig < Anyway::Config
include Docuconf::Anyway
# Read PORT, DATABASE_URL, ... with no ORDERS_ prefix.
env_prefix ""
attr_config :database_url,
port: 8080, log_level: "info", allowed_origins: ["http://localhost:3000"],
request_timeout: "30s", worker_count: 4
required :database_url
describe :port, "HTTP listen port", min: 1, max: 65_535
describe :log_level, "Minimum log level", values: %w[debug info warn error]
describe :database_url, "Postgres connection string for orders", type: :url, schemes: %w[postgres],
max_length: 2048, secret: true # never printed, and the contract marks it secret
describe :allowed_origins, "CORS origins allowed to call the API", min_items: 1
# How long the server works on one request before it gives up.
#
# Raise it when clients upload large order batches. Keep it below the load
# balancer's idle timeout, or the client sees a reset rather than a +504+.
describe :request_timeout, "Time allowed to handle one request", min: "1s", max: "5m"
describe :worker_count, "Background workers processing orders", min: 1, max: 64
end2 · The contract your platform checks
// Code generated by docuconf. DO NOT EDIT.
package orders
import "docuconf.dev/contract"
contract.#Contract & {
apiVersion: "docuconf.dev/v1alpha1"
kind: "ConfigContract"
metadata: {
name: "orders-api"
generator: {
language: "ruby"
sdk: "docuconf-anyway"
version: "0.1.0"
}
}
vars: {
ALLOWED_ORIGINS: {
type: "list"
description: "CORS origins allowed to call the API"
configKey: "orders.allowed_origins"
items: "string"
encoding: "csv"
separator: ","
minItems: 1
default: ["http://localhost:3000"]
}
DATABASE_URL: {
type: "url"
description: "Postgres connection string for orders"
required: true
secret: true
configKey: "orders.database_url"
schemes: ["postgres"]
maxLength: 2048
}
LOG_LEVEL: {
type: "enum"
description: "Minimum log level"
configKey: "orders.log_level"
values: ["debug", "info", "warn", "error"]
default: "info"
}
PORT: {
type: "int"
description: "HTTP listen port"
configKey: "orders.port"
min: 1
max: 65535
default: 8080
}
REQUEST_TIMEOUT: {
type: "duration"
description: "Time allowed to handle one request"
details: "How long the server works on one request before it gives up.\n\nRaise it when clients upload large order batches. Keep it below the load\nbalancer's idle timeout, or the client sees a reset rather than a `504`."
configKey: "orders.request_timeout"
encoding: "iso8601"
min: "1s"
max: "5m"
default: "30s"
}
WORKER_COUNT: {
type: "int"
description: "Background workers processing orders"
configKey: "orders.worker_count"
min: 1
max: 64
default: 4
}
}
}1 · Your declaration, as you write it today
package dev.docuconf.examples.orders;
import dev.docuconf.Docuconf;
import dev.docuconf.EnumCase;
import dev.docuconf.MaxLength;
import dev.docuconf.Redacted;
import dev.docuconf.Secret;
import dev.docuconf.UrlSchemes;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import java.net.URI;
import java.time.Duration;
import java.util.List;
import org.hibernate.validator.constraints.time.DurationMax;
import org.hibernate.validator.constraints.time.DurationMin;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.DefaultValue;
import org.springframework.validation.annotation.Validated;
/**
* Settings of the orders service. Each property is the environment variable Spring binds to it: {@code port} is
* {@code ORDERS_PORT}, {@code databaseUrl} is {@code ORDERS_DATABASEURL}. The first sentence of each property's
* Javadoc is the contract's description, and the rest is its details, longer docs for {@code docuconf docs}.
*
* @param port HTTP listen port
* @param logLevel Minimum level of the log lines the service writes
* @param databaseUrl Postgres connection URL for the orders database
* @param allowedOrigins Origins allowed to call the API from a browser
* @param requestTimeout Time allowed to answer one request
* @param workerCount Background workers that process new orders
* <p>Each worker holds one connection from the pool of {@code databaseUrl}, so keep this below the
* database's connection limit.
* <ul>
* <li>Raise it when the order queue backs up.</li>
* <li>Lower it when the database is the bottleneck.</li>
* </ul>
*/
@Docuconf(service = "orders", enumCase = EnumCase.LOWER)
@Validated
@ConfigurationProperties("orders")
public record OrdersProperties(
@Min(1) @Max(65535) @DefaultValue("8080") int port,
@DefaultValue("INFO") LogLevel logLevel,
// @Secret: the platform must supply it from a Secret, and docuconf never prints it. @MaxLength bounds the
// URL in characters; a longer one fails startup with out_of_range.
@NotNull @Secret @UrlSchemes("postgres") @MaxLength(2048) URI databaseUrl,
@NotEmpty @DefaultValue("http://localhost:3000") List<String> allowedOrigins,
@DurationMin(seconds = 1) @DurationMax(minutes = 5) @DefaultValue("30s") Duration requestTimeout,
@Min(1) @Max(64) @DefaultValue("4") int workerCount) {
/** Log levels. The contract spells them in lower case (enumCase); the app accepts any case, as Spring does. */
public enum LogLevel { DEBUG, INFO, WARN, ERROR }
/** Prints the secret as [redacted]; a record's generated toString() would print it. */
@Override
public String toString() {
return Redacted.toString(this);
}
}2 · The contract your platform checks
// Code generated by docuconf. DO NOT EDIT.
package orders
import "docuconf.dev/contract"
contract.#Contract & {
apiVersion: "docuconf.dev/v1alpha1"
kind: "ConfigContract"
metadata: {
name: "orders"
appVersion: "1.0.0"
generator: {language: "java", sdk: "docuconf-spring", version: "0.1.0-SNAPSHOT"}
}
vars: {
ORDERS_ALLOWEDORIGINS: {
type: "list"
description: "Origins allowed to call the API from a browser"
configKey: "orders.allowed-origins"
items: "string"
encoding: "csv"
minItems: 1
default: ["http://localhost:3000"]
}
ORDERS_DATABASEURL: {
type: "url"
description: "Postgres connection URL for the orders database"
required: true
secret: true
configKey: "orders.database-url"
schemes: ["postgres"]
maxLength: 2048
}
ORDERS_LOGLEVEL: {
type: "enum"
description: "Minimum level of the log lines the service writes"
configKey: "orders.log-level"
values: ["debug", "info", "warn", "error"]
default: "info"
}
ORDERS_PORT: {
type: "int"
description: "HTTP listen port"
configKey: "orders.port"
min: 1
max: 65535
default: 8080
}
ORDERS_REQUESTTIMEOUT: {
type: "duration"
description: "Time allowed to answer one request"
configKey: "orders.request-timeout"
encoding: "iso8601"
min: "1s"
max: "5m"
default: "30s"
}
ORDERS_WORKERCOUNT: {
type: "int"
description: "Background workers that process new orders"
details: "Each worker holds one connection from the pool of `databaseUrl`, so keep this below the database's connection limit.\n\n- Raise it when the order queue backs up.\n- Lower it when the database is the bottleneck."
configKey: "orders.worker-count"
min: 1
max: 64
default: 4
}
}
}1 · Your declaration, as you write it today
@DocuconfService(name = "orders")
data class OrdersConfig(
@Doc("HTTP listen port") @Min(1) @Max(65535) val port: Int = 8080,
@Doc("Minimum level of log messages") val logLevel: LogLevel = LogLevel.INFO,
// A Hoplite Secret is exported with `secret: true`; its value never appears in errors or toString.
// @Length(max) on a URL is its maxLength in characters; a longer one fails the boot with out_of_range.
@Doc("Postgres connection URL for the orders database") @Schemes("postgres") @Length(max = 2048) val databaseUrl: Secret,
@Doc("Origins allowed to call the API (CORS)") @Items(min = 1) val allowedOrigins: List<String> = listOf("http://localhost:3000"),
@Doc("Time limit for handling one request") @DurationMin("1s") @DurationMax("5m") val requestTimeout: Duration = Duration.ofSeconds(30),
/**
* Number of background workers that process orders
*
* A KDoc works instead of @Doc: its first sentence is the description, and the rest is the details,
* longer docs for `docuconf docs`. Each worker holds one connection from the pool of [databaseUrl],
* so keep this below the database's connection limit.
*
* - Raise it when the order queue backs up.
* - Lower it when the database is the bottleneck.
*/
@Min(1) @Max(64) val workerCount: Int = 4,
)2 · The contract your platform checks
// Code generated by docuconf. DO NOT EDIT.
package orders
import "docuconf.dev/contract"
contract.#Contract & {
apiVersion: "docuconf.dev/v1alpha1"
kind: "ConfigContract"
metadata: {
name: "orders"
generator: {language: "kotlin", sdk: "docuconf-hoplite", version: "0.1.0"}
}
vars: {
ALLOWED_ORIGINS: {
type: "list"
description: "Origins allowed to call the API (CORS)"
configKey: "allowedOrigins"
items: "string"
encoding: "csv"
minItems: 1
default: ["http://localhost:3000"]
}
DATABASE_URL: {
type: "url"
description: "Postgres connection URL for the orders database"
required: true
secret: true
configKey: "databaseUrl"
schemes: ["postgres"]
maxLength: 2048
}
LOG_LEVEL: {
type: "enum"
description: "Minimum level of log messages"
configKey: "logLevel"
values: ["debug", "info", "warn", "error"]
default: "info"
}
PORT: {
type: "int"
description: "HTTP listen port"
configKey: "port"
min: 1
max: 65535
default: 8080
}
REQUEST_TIMEOUT: {
type: "duration"
description: "Time limit for handling one request"
configKey: "requestTimeout"
encoding: "iso8601"
min: "1s"
max: "5m"
default: "30s"
}
WORKER_COUNT: {
type: "int"
description: "Number of background workers that process orders"
details: "A KDoc works instead of @Doc: its first sentence is the description, and the rest is the details,\nlonger docs for `docuconf docs`. Each worker holds one connection from the pool of `databaseUrl`,\nso keep this below the database's connection limit.\n\n- Raise it when the order queue backs up.\n- Lower it when the database is the bottleneck."
configKey: "workerCount"
min: 1
max: 64
default: 4
}
}
}1 · Your declaration, as you write it today
/// The service's configuration. The first paragraph of each `///` comment
/// is the variable's description in the contract and the rest its details,
/// and the Rust type picks its contract type.
#[derive(Debug, Deserialize, Docuconf)]
struct Config {
/// HTTP listen port.
#[docuconf(default = 8080, min = 1)]
port: u16,
/// Minimum log level emitted.
#[docuconf(default = "info")]
log_level: LogLevel,
/// Postgres connection string for the orders database.
// `Secret` marks it `secret: true`; `schemes` makes it a `url`, and
// `max_length` caps it in characters.
#[docuconf(schemes("postgres"), max_length = 2048)]
database_url: Secret<String>,
/// Browser origins allowed to call the API.
#[docuconf(default = ["http://localhost:3000"], min_items = 1)]
allowed_origins: Vec<String>,
/// Time allowed to read and answer one request.
#[docuconf(default = "30s", min = "1s", max = "5m")]
#[serde(with = "docuconf::humantime_serde")]
request_timeout: Duration,
/// Threads serving requests.
///
/// Each worker answers one connection at a time, so this is also the
/// number of requests served at once. Raise it when requests queue up;
/// each worker holds a [`std::thread`] stack.
///
/// Keep it at or below the database pool size:
///
/// - one connection per worker;
/// - plus one for migrations.
#[docuconf(default = 4, min = 1, max = 64)]
worker_count: u8,
}2 · The contract your platform checks
// Code generated by docuconf. DO NOT EDIT.
package orders
import "docuconf.dev/contract"
contract.#Contract & {
apiVersion: "docuconf.dev/v1alpha1"
kind: "ConfigContract"
metadata: {
name: "orders-api"
generator: {
language: "rust"
sdk: "docuconf"
version: "0.1.0"
}
}
vars: {
ALLOWED_ORIGINS: {
type: "list"
description: "Browser origins allowed to call the API"
default: ["http://localhost:3000"]
items: "string"
encoding: "json"
minItems: 1
}
DATABASE_URL: {
type: "url"
description: "Postgres connection string for the orders database"
required: true
secret: true
schemes: ["postgres"]
maxLength: 2048
}
LOG_LEVEL: {
type: "enum"
description: "Minimum log level emitted"
default: "info"
values: ["debug", "info", "warn", "error"]
}
PORT: {
type: "int"
description: "HTTP listen port"
default: 8080
min: 1
max: 65535
}
REQUEST_TIMEOUT: {
type: "duration"
description: "Time allowed to read and answer one request"
default: "30s"
encoding: "go"
min: "1s"
max: "5m"
}
WORKER_COUNT: {
type: "int"
description: "Threads serving requests"
details: "Each worker answers one connection at a time, so this is also the\nnumber of requests served at once. Raise it when requests queue up;\neach worker holds a `std::thread` stack.\n\nKeep it at or below the database pool size:\n\n- one connection per worker;\n- plus one for migrations."
default: 4
min: 1
max: 64
}
}
}1 · Your declaration, as you write it today
enum LogLevel: String, ConfigEnum {
case debug, info, warn, error
}2 · The contract your platform checks
// Code generated by docuconf. DO NOT EDIT.
package orders
import "docuconf.dev/contract"
contract.#Contract & {
apiVersion: "docuconf.dev/v1alpha1"
kind: "ConfigContract"
metadata: {
name: "orders"
generator: {language: "swift", sdk: "docuconf-swift", version: "0.1.0"}
}
vars: {
ALLOWED_ORIGINS: {
type: "list"
description: "Origins allowed to call the API (CORS)"
configKey: "allowed.origins"
items: "string"
encoding: "csv"
minItems: 1
default: ["http://localhost:3000"]
}
DATABASE_URL: {
type: "url"
description: "Postgres connection string for the orders database"
required: true
secret: true
configKey: "database.url"
schemes: ["postgres"]
maxLength: 2048
}
LOG_LEVEL: {
type: "enum"
description: "Minimum log level"
configKey: "log.level"
values: ["debug", "info", "warn", "error"]
default: "info"
}
PORT: {
type: "int"
description: "HTTP listen port"
configKey: "port"
min: 1
max: 65535
default: 8080
}
REQUEST_TIMEOUT: {
type: "duration"
description: "Timeout for one request"
configKey: "request.timeout"
encoding: "seconds"
min: "1s"
max: "5m"
default: "30s"
}
WORKER_COUNT: {
type: "int"
description: "Number of background order workers"
details: "Each worker takes one order at a time from the queue and holds one database connection, so keep this\nat or below the pool size:\n\n- one connection per worker;\n- plus one for the HTTP handlers."
configKey: "worker.count"
min: 1
max: 64
default: 4
}
}
}1 · Your declaration, as you write it today
defmodule Orders.Env do
@moduledoc "Every environment variable the orders service reads."
use Docuconf, name: "orders"
env :port, :integer, description: "HTTP listen port", default: 8080, min: 1, max: 65535
# Atom values come back as atoms, ready for Logger.
env :log_level, {:in, [:debug, :info, :warning, :error]},
description: "Minimum log level",
default: :info
# A secret: docuconf never prints its value (not in errors, and not when
# Orders.Env is inspected or logged), and the contract tells the platform
# to supply it from a Kubernetes Secret.
secret :database_url, :url,
description: "Postgres connection string",
required: true,
schemes: ["postgres"],
max_length: 2048
env :allowed_origins, {:list, :string},
description: "Origins allowed to call the API (CORS)",
min_items: 1,
default: ["http://localhost:3000"]
@doc """
Time limit for one request.
Raise it when clients upload large order batches. Keep it below the load
balancer's idle timeout, or the client sees a reset rather than a `504`.
"""
env :request_timeout, :duration,
min: "1s",
max: "5m",
default: "30s"
env :worker_count, :integer,
description: "Order processing workers",
min: 1,
max: 64,
default: 4
end2 · The contract your platform checks
// Code generated by docuconf. DO NOT EDIT.
package orders
import "docuconf.dev/contract"
contract.#Contract & {
apiVersion: "docuconf.dev/v1alpha1"
kind: "ConfigContract"
metadata: {
name: "orders"
appVersion: "1.0.0"
generator: {
language: "elixir"
sdk: "docuconf"
version: "0.1.0"
}
}
vars: {
ALLOWED_ORIGINS: {
type: "list"
description: "Origins allowed to call the API (CORS)"
items: "string"
encoding: "csv"
separator: ","
minItems: 1
default: ["http://localhost:3000"]
}
DATABASE_URL: {
type: "url"
description: "Postgres connection string"
required: true
secret: true
schemes: ["postgres"]
maxLength: 2048
}
LOG_LEVEL: {
type: "enum"
description: "Minimum log level"
values: ["debug", "info", "warning", "error"]
default: "info"
}
PORT: {
type: "int"
description: "HTTP listen port"
min: 1
max: 65535
default: 8080
}
REQUEST_TIMEOUT: {
type: "duration"
description: "Time limit for one request"
details: "Raise it when clients upload large order batches. Keep it below the load\nbalancer's idle timeout, or the client sees a reset rather than a `504`."
encoding: "go"
min: "1s"
max: "5m"
default: "30s"
}
WORKER_COUNT: {
type: "int"
description: "Order processing workers"
min: 1
max: 64
default: 4
}
}
}1 · Your declaration, as you write it today
pub fn spec() -> docuconf.Spec(Config) {
use port <- docuconf.env(
docuconf.int("PORT", "HTTP listen port")
|> docuconf.min_int(1)
|> docuconf.max_int(65_535)
|> docuconf.default(8080),
)
use log_level <- docuconf.env(
docuconf.enum("LOG_LEVEL", "Minimum log level emitted", log_levels)
|> docuconf.default(wisp.InfoLevel),
)
// A secret is exported as `secret: true`: the platform supplies it from a
// Secret. docuconf never prints its value, and the app gets a
// `docuconf.Secret`, which prints redacted; `docuconf.reveal` reads it.
use database_url <- docuconf.env(
docuconf.url("DATABASE_URL", "Primary Postgres connection string")
|> docuconf.schemes(["postgres"])
// At most 2048 characters; a longer URL fails the boot with out_of_range.
|> docuconf.max_length(2048)
|> docuconf.secret
|> docuconf.required,
)
use allowed_origins <- docuconf.env(
docuconf.string_list(
"ALLOWED_ORIGINS",
"Origins allowed to call the API (CORS), comma-separated",
separator: ",",
)
|> docuconf.min_items(1)
|> docuconf.default(["http://localhost:3000"]),
)
use request_timeout <- docuconf.env(
docuconf.duration("REQUEST_TIMEOUT", "Timeout for one API request")
// Longer docs for `docuconf docs`, in Markdown. Never read at runtime.
|> docuconf.details(
"Raise it when clients upload large order batches. Keep it below the
load balancer's idle timeout, or the client sees a reset rather than a
`504`.",
)
|> docuconf.min_duration(duration.seconds(1))
|> docuconf.max_duration(duration.minutes(5))
|> docuconf.default(duration.seconds(30)),
)
use worker_count <- docuconf.env(
docuconf.int("WORKER_COUNT", "Background workers that process orders")
|> docuconf.min_int(1)
|> docuconf.max_int(64)
|> docuconf.default(4),
)
// Each `use` above bound a handle; `build` reads the values once they
// have all loaded and passed their checks.
use v <- docuconf.build
Config(
port: port(v),
log_level: log_level(v),
database_url: database_url(v),
allowed_origins: allowed_origins(v),
request_timeout: request_timeout(v),
worker_count: worker_count(v),
)
}2 · The contract your platform checks
// Code generated by docuconf. DO NOT EDIT.
package orders
import "docuconf.dev/contract"
contract.#Contract & {
apiVersion: "docuconf.dev/v1alpha1"
kind: "ConfigContract"
metadata: {
name: "orders"
generator: {
language: "gleam"
sdk: "docuconf"
version: "0.1.0"
}
}
vars: {
ALLOWED_ORIGINS: {
type: "list"
description: "Origins allowed to call the API (CORS), comma-separated"
items: "string"
encoding: "csv"
separator: ","
minItems: 1
default: ["http://localhost:3000"]
}
DATABASE_URL: {
type: "url"
description: "Primary Postgres connection string"
required: true
secret: true
schemes: ["postgres"]
maxLength: 2048
}
LOG_LEVEL: {
type: "enum"
description: "Minimum log level emitted"
values: ["debug", "info", "warn", "error"]
default: "info"
}
PORT: {
type: "int"
description: "HTTP listen port"
min: 1
max: 65535
default: 8080
}
REQUEST_TIMEOUT: {
type: "duration"
description: "Timeout for one API request"
details: "Raise it when clients upload large order batches. Keep it below the\nload balancer's idle timeout, or the client sees a reset rather than a\n`504`."
encoding: "go"
min: "1s"
max: "5m"
default: "30s"
}
WORKER_COUNT: {
type: "int"
description: "Background workers that process orders"
min: 1
max: 64
default: 4
}
}
}1 · Your declaration, as you write it today
CLI::App app{"orders: a small HTTP service configured with docuconf"};
// The service name becomes the contract's metadata.name.
docuconf::Declaration config{app, "orders"};
// Each variable is read from its environment variable and checked at
// boot; --help lists them all. PORT is also a command-line flag (--port)
// for local runs; the platform only ever sets the environment.
int port = 0;
config.add_var("PORT", port, "HTTP listen port").range(1, 65535).default_val(8080).flag();
std::string log_level;
config.add_var("LOG_LEVEL", log_level, "Minimum log level emitted")
.values({"debug", "info", "warn", "error"})
.default_val("info");
// A secret: never printed, never given a default, supplied by the
// platform from a Kubernetes Secret.
std::string database_url;
config.add_var("DATABASE_URL", database_url, "Primary Postgres connection string")
.secret()
.required()
.schemes({"postgres"})
.max_length(2048);
std::vector<std::string> allowed_origins;
config.add_var("ALLOWED_ORIGINS", allowed_origins, "Origins allowed to call the API (CORS)")
.min_items(1)
.default_val({"http://localhost:3000"});
std::chrono::milliseconds request_timeout{};
config.add_var("REQUEST_TIMEOUT", request_timeout, "Time allowed to read a request")
.range("1s", "5m")
.default_val("30s");
int worker_count = 0;
config.add_var("WORKER_COUNT", worker_count)
.doc(R"(
/// Number of request worker threads.
///
/// Each worker answers one connection at a time, so this is also the
/// number of requests served at once. Raise it when requests queue up.
///
/// Keep it at or below the database pool size:
/// @li one connection per worker;
/// @li plus one for migrations.
)")
.range(1, 64)
.default_val(4);2 · The contract your platform checks
// Code generated by docuconf. DO NOT EDIT.
package orders
import "docuconf.dev/contract"
contract.#Contract & {
apiVersion: "docuconf.dev/v1alpha1"
kind: "ConfigContract"
metadata: {
name: "orders"
generator: {
language: "cpp"
sdk: "docuconf-cpp"
version: "0.1.0"
}
}
vars: {
ALLOWED_ORIGINS: {
type: "list"
description: "Origins allowed to call the API (CORS)"
default: ["http://localhost:3000"]
items: "string"
encoding: "csv"
separator: ","
minItems: 1
}
DATABASE_URL: {
type: "url"
description: "Primary Postgres connection string"
required: true
secret: true
maxLength: 2048
schemes: ["postgres"]
}
LOG_LEVEL: {
type: "enum"
description: "Minimum log level emitted"
default: "info"
values: ["debug", "info", "warn", "error"]
}
PORT: {
type: "int"
description: "HTTP listen port"
default: 8080
min: 1
max: 65535
}
REQUEST_TIMEOUT: {
type: "duration"
description: "Time allowed to read a request"
default: "30s"
min: "1s"
max: "5m"
encoding: "go"
}
WORKER_COUNT: {
type: "int"
description: "Number of request worker threads"
details: "Each worker answers one connection at a time, so this is also the\nnumber of requests served at once. Raise it when requests queue up.\n\nKeep it at or below the database pool size:\n- one connection per worker;\n- plus one for migrations."
default: 4
min: 1
max: 64
}
}
}1 · Your declaration, as you write it today
<?php
use Docuconf\Laravel\Env;
// Env:: is env() with a type, rules and a description. Each call returns the
// typed value, and the app refuses to boot if any of them is wrong.
return [
'port' => Env::int('PORT', 'HTTP listen port', default: 8080, min: 1, max: 65535),
// ORDERS_, not LOG_LEVEL: Laravel's own config/logging.php reads LOG_LEVEL.
'log_level' => Env::enum('ORDERS_LOG_LEVEL', 'Minimum level the orders code logs', ['debug', 'info', 'warn', 'error'], default: 'info'),
// A secret: never printed, and the platform must supply it from a Secret.
'database_url' => Env::url('DATABASE_URL', 'Orders database connection string', required: true, schemes: ['postgres', 'postgresql'], secret: true, maxLength: 2048),
'allowed_origins' => Env::list('ALLOWED_ORIGINS', 'Origins allowed to call the API (CORS)', default: ['http://localhost:3000'], minItems: 1),
// A Docuconf\Duration; written "30s", "1m30s" in the env. The PHPDoc
// comment documents it: its first paragraph is the description, and the
// rest is exported as details, for `docuconf docs`.
/**
* Timeout for each request.
*
* Raise it when clients upload large order batches. Keep it below the
* load balancer's idle timeout, or the client sees a reset rather than
* a `504`.
*/
'request_timeout' => Env::duration('REQUEST_TIMEOUT', default: '30s', min: '1s', max: '5m'),
'worker_count' => Env::int('WORKER_COUNT', 'Number of background workers', default: 4, min: 1, max: 64),
];2 · The contract your platform checks
// Code generated by docuconf. DO NOT EDIT.
package orders
import "docuconf.dev/contract"
contract.#Contract & {
apiVersion: "docuconf.dev/v1alpha1"
kind: "ConfigContract"
metadata: {
name: "orders"
generator: {
language: "php"
sdk: "docuconf/docuconf"
version: "0.1.0"
}
}
vars: {
ALLOWED_ORIGINS: {
type: "list"
description: "Origins allowed to call the API (CORS)"
items: "string"
encoding: "csv"
separator: ","
minItems: 1
default: ["http://localhost:3000"]
}
DATABASE_URL: {
type: "url"
description: "Orders database connection string"
required: true
secret: true
schemes: ["postgres", "postgresql"]
maxLength: 2048
}
ORDERS_LOG_LEVEL: {
type: "enum"
description: "Minimum level the orders code logs"
values: ["debug", "info", "warn", "error"]
default: "info"
}
PORT: {
type: "int"
description: "HTTP listen port"
min: 1
max: 65535
default: 8080
}
REQUEST_TIMEOUT: {
type: "duration"
description: "Timeout for each request"
details: "Raise it when clients upload large order batches. Keep it below the\nload balancer's idle timeout, or the client sees a reset rather than\na `504`."
encoding: "go"
min: "1s"
max: "5m"
default: "30s"
}
WORKER_COUNT: {
type: "int"
description: "Number of background workers"
min: 1
max: 64
default: 4
}
}
}1 · Your declaration, as you write it today
# Every variable the service reads, written as in contract.cue. Read them
# with %env(docuconf:NAME)%: the value docuconf validated, typed.
docuconf:
name: orders-symfony
vars:
PORT: {type: int, description: HTTP listen port, min: 1, max: 65535, default: 8080}
LOG_LEVEL: {type: enum, description: Minimum log level, values: [debug, info, warn, error], default: info}
DATABASE_URL: {type: url, description: Orders database connection string, required: true, secret: true, schemes: [postgres, postgresql], maxLength: 2048}
ALLOWED_ORIGINS: {type: list, description: Origins allowed to call the API (CORS), items: string, minItems: 1, default: ['http://localhost:3000']}
REQUEST_TIMEOUT: {type: duration, description: Timeout for each request, min: 1s, max: 5m, default: 30s}
WORKER_COUNT: {type: int, description: Number of background workers, min: 1, max: 64, default: 4}
parameters:
orders.port: '%env(docuconf:PORT)%'
orders.request_timeout_seconds: '%env(docuconf_seconds:REQUEST_TIMEOUT)%'2 · The contract your platform checks
// Code generated by docuconf. DO NOT EDIT.
package orders_symfony
import "docuconf.dev/contract"
contract.#Contract & {
apiVersion: "docuconf.dev/v1alpha1"
kind: "ConfigContract"
metadata: {
name: "orders-symfony"
generator: {
language: "php"
sdk: "docuconf/docuconf"
version: "0.1.0"
}
}
vars: {
ALLOWED_ORIGINS: {
type: "list"
description: "Origins allowed to call the API (CORS)"
items: "string"
encoding: "csv"
separator: ","
minItems: 1
default: ["http://localhost:3000"]
}
DATABASE_URL: {
type: "url"
description: "Orders database connection string"
required: true
secret: true
schemes: ["postgres", "postgresql"]
maxLength: 2048
}
LOG_LEVEL: {
type: "enum"
description: "Minimum log level"
values: ["debug", "info", "warn", "error"]
default: "info"
}
PORT: {
type: "int"
description: "HTTP listen port"
min: 1
max: 65535
default: 8080
}
REQUEST_TIMEOUT: {
type: "duration"
description: "Timeout for each request"
encoding: "go"
min: "1s"
max: "5m"
default: "30s"
}
WORKER_COUNT: {
type: "int"
description: "Number of background workers"
min: 1
max: 64
default: 4
}
}
}1 · Your declaration, as you write it today
*> Configuration of the ORDERS-BATCH job, read from the
*> environment by the generated loader ORDCFG.
*> @service orders-batch @prefix CFG- @program ORDCFG
01 ORDERS-CONFIG.
*> Port of the Prometheus metrics endpoint
*> @min 1 @max 65535 @default 8080
05 CFG-PORT PIC 9(5).
*> Log verbosity
*> @default info
05 CFG-LOG-LEVEL PIC X(5).
88 LOG-DEBUG VALUE "debug".
88 LOG-INFO VALUE "info".
88 LOG-WARN VALUE "warn".
88 LOG-ERROR VALUE "error".
*> Postgres connection string of the orders database
*> @type url @schemes postgres @secret @required
05 CFG-DATABASE-URL PIC X(200).
*> Origins whose orders the job accepts
*> @min-items 1 @default "http://localhost:3000"
*> @count CFG-ORIGIN-COUNT
05 CFG-ALLOWED-ORIGINS PIC X(64) OCCURS 8 TIMES.
05 CFG-ORIGIN-COUNT PIC 9(2).
*> Time allowed for each database call, in milliseconds
*> @unit ms @min 1s @max 5m @default 30s
05 CFG-REQUEST-TIMEOUT PIC 9(6).
*> Number of workers that share the input
*>
*> Each worker reads its share of the orders file and holds
*> one database connection, so keep this at or below the
*> pool size:
*>
*> - one connection per worker;
*> - plus one for the summary step.
*> @min 1 @max 64 @default 4
05 CFG-WORKER-COUNT PIC 9(2).
*> The orders to summarise, one per line
*> @file orders text @path /data/orders.txt
*> @path-env ORDERS_FILE @required @max-size 1Mi
05 CFG-ORDERS-PATH PIC X(256).2 · The contract your platform checks
// Code generated by docuconf. DO NOT EDIT.
package orders_batch
import "docuconf.dev/contract"
contract.#Contract & {
apiVersion: "docuconf.dev/v1alpha1"
kind: "ConfigContract"
metadata: {
name: "orders-batch"
generator: {
language: "cobol"
sdk: "docuconf-cobol"
version: "0.1.0"
}
}
vars: {
ALLOWED_ORIGINS: {
type: "list"
description: "Origins whose orders the job accepts"
default: ["http://localhost:3000"]
encoding: "csv"
items: "string"
separator: ","
minItems: 1
maxItems: 8
itemMaxLength: 64
}
DATABASE_URL: {
type: "url"
description: "Postgres connection string of the orders database"
required: true
secret: true
maxLength: 200
schemes: ["postgres"]
}
LOG_LEVEL: {
type: "enum"
description: "Log verbosity"
default: "info"
values: ["debug", "info", "warn", "error"]
}
PORT: {
type: "int"
description: "Port of the Prometheus metrics endpoint"
default: 8080
min: 1
max: 65535
}
REQUEST_TIMEOUT: {
type: "duration"
description: "Time allowed for each database call, in milliseconds"
default: "30s"
min: "1s"
max: "5m"
encoding: "go"
}
WORKER_COUNT: {
type: "int"
description: "Number of workers that share the input"
details: "Each worker reads its share of the orders file and holds\none database connection, so keep this at or below the\npool size:\n\n- one connection per worker;\n- plus one for the summary step."
default: 4
min: 1
max: 64
}
}
files: {
orders: {
type: "text"
description: "The orders to summarise, one per line"
required: true
path: "/data/orders.txt"
pathEnv: "ORDERS_FILE"
maxSize: 1048576
}
}
}Principles
What docuconf believes.
Your library, not ours
docuconf extends the leading env library in each language. Adopting it means adding a package and some metadata, not rewriting your config.
One contract, any language
A Rails app and a .NET service produce the same kind of contract, so the platform validates both the same way. 16 SDKs pass one shared conformance suite.
Fail before deploy
Missing, mistyped and out-of-range values are caught at composition time, not at 2 a.m. by a crash loop.
Secrets stay secret
Contracts never hold secret values. The platform supplies secrets only as references, and errors never print them.
Policy stays with the platform
The app says what it accepts; the platform says what each environment allows. Neither edits the other.
Open by design
A specification first, with a conformance suite so independently built SDKs provably agree.
Languages
Built for platform teams running many languages.
| SDK | Builds on | Status |
|---|---|---|
| Go | caarlos0/env v11 | v0.1 alpha · conformance 134/134 |
| TypeScript (T3 Env) | T3 Env + Zod 4 | v0.1 alpha · conformance 131/134 |
| TypeScript (NestJS) | @nestjs/config + class-validator | v0.1 alpha · conformance 131/134 |
| .NET | Options pattern + appsettings | v0.1 alpha · conformance 132/134 |
| Python | pydantic-settings 2 | v0.1 alpha · conformance 134/134 |
| Ruby | anyway_config 2 | v0.1 alpha · conformance 134/134 |
| Java (Spring Boot) | Spring Boot 3 / 4 @ConfigurationProperties | v0.1 alpha · conformance 134/134 |
| Kotlin | Hoplite 3 | v0.1 alpha · conformance 134/134 |
| Rust | figment + serde | v0.1 alpha · conformance 134/134 |
| Swift | swift-configuration | v0.1 alpha · conformance 132/134 |
| Elixir | config/runtime.exs | v0.1 alpha · conformance 134/134 |
| Gleam | envoy + gleam/dynamic/decode | v0.1 alpha · conformance 132/134 |
| C++ | CLI11 | v0.1 alpha · conformance 134/134 |
| PHP (Laravel) | Laravel config + vlucas/phpdotenv | v0.1 alpha · conformance 134/134 |
| PHP (Symfony) | Symfony config + %env()% processors | v0.1 alpha · conformance 134/134 |
| COBOL | GnuCOBOL copybooks + docuconf exec | v0.1 alpha · conformance 134/134 |
None is on a package registry yet; each Get started page installs from git. Conformance is the shared suite of 134 cases every SDK runs. Compare what each SDK supports.
A deliberate boundary
Config is not feature flags.
A variable belongs in a contract if, and only if, changing it needs a rollout. Runtime toggles belong in a flag system such as OpenFeature.
Why the line matters →Early days
Help shape the spec.
The contract specification is a draft and every SDK is a v0.1 alpha. One good comment can still change the design.