How to Organize Configuration Files in Go Projects: The /configs Directory Standard

Place all configuration templates and default files in a dedicated /configs directory at the repository root to keep operational assets separate from source code and enable environment-specific customization.

The golang-standards/project-layout repository establishes a widely adopted convention for structuring Go applications. Organizing configuration files in Go projects requires a predictable location that tooling can discover without cluttering your cmd/ or pkg/ directories. This standard places all configuration-related assets in a top-level /configs folder, separating deployment concerns from implementation logic.

The /configs Directory Structure and Purpose

According to the project-layout standard, the /configs directory at the repository root holds configuration file templates or default configs. The repository's README.md explicitly states this folder is designed for files that can be copied or rendered for different environments.

The configs/README.md file provides specific guidance: "Put your confd or consul-template template files here." This location serves as the single source of truth for all configuration artifacts, distinct from the application source code and aligned with other operational folders like /init, /scripts, and /build.

Benefits of Separating Configuration from Source Code

Maintaining a dedicated /configs directory delivers three primary advantages:

  • Versioning and Auditing: Keeping configuration separate from source code makes it easier to track changes to operational settings independently of feature development.
  • Environment Customization: Default configs stored in /configs can be copied and modified for development, staging, and production without modifying the codebase.
  • Tooling Integration: Libraries like Viper and envconfig can load files from a predictable location without cluttering the cmd/ or pkg/ hierarchy.

Typical Organization Inside /configs

While the standard does not mandate specific subdirectories, the following structure represents best practices for organizing configuration files:

  • json/ – Contains JSON default configs such as config.dev.json and config.prod.json.
  • yaml/ – Stores YAML equivalents like config.yaml for environment-specific settings.
  • templates/ – Holds text/template files used by tools like confd for dynamic configuration generation.
  • example/ – Provides sample config files for new developers to copy and customize.
  • scripts/ – Includes helper scripts such as render.sh that process template files.

JSON and YAML Defaults

Place environment-specific defaults in configs/json/ or configs/yaml/ subdirectories. This allows applications to load config.dev.json for local development while operations teams mount config.prod.json in production containers.

Template Files for Dynamic Configuration

Store confd or consul-template template files in configs/templates/. These files support dynamic configuration generation based on environment variables or service discovery, which is essential for containerized deployments orchestrated by Kubernetes or Docker Swarm.

Loading Configuration Files in Go

Once files are organized in /configs, your application needs reliable methods to load them. The following examples demonstrate standard patterns using popular configuration libraries.

Using Viper to Load from /configs

Viper supports multiple config formats and can be pointed directly at the /configs directory. Create a reusable loader in pkg/config/config.go:

package config

import (
	"log"
	"strings"

	"github.com/spf13/viper"
)

// Load reads a config file from the project's /configs directory.
// env may be "dev", "staging", "prod", etc.
func Load(env string) {
	// Directory relative to the project root.
	viper.AddConfigPath("configs")
	// Look for both JSON and YAML files.
	for _, ext := range []string{"json", "yaml", "yml"} {
		viper.SetConfigName("config." + env)
		viper.SetConfigType(ext)
		if err := viper.ReadInConfig(); err == nil {
			break
		}
	}

	if err := viper.ReadInConfig(); err != nil {
		log.Fatalf("cannot read config for %s: %v", env, err)
	}

	// Example: bind environment variables (override file values)
	viper.AutomaticEnv()
	viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
}

This implementation follows the project-layout convention by placing reusable configuration logic in pkg/config/, while the actual config files remain in /configs at the repository root.

Using envconfig with Example Files

For projects preferring environment variables over files, place a .env.example file in configs/ to document required variables. Implement the loader in internal/config/app_config.go:

package config

import (
	"log"

	"github.com/kelseyhightower/envconfig"
)

// Settings defines the configuration schema.
type Settings struct {
	Port        int    `envconfig:"APP_PORT" default:"8080"`
	DatabaseURL string `envconfig:"DATABASE_URL" required:"true"`
}

// LoadEnv reads variables, falling back to a .env file placed in /configs.
func LoadEnv() Settings {
	var s Settings
	if err := envconfig.Process("", &s); err != nil {
		log.Fatalf("failed to parse env: %v", err)
	}
	return s
}

Key Files in the Configuration Layout

Understanding the standard requires familiarity with these specific files:

  • README.md#configs – The main repository documentation describing the /configs purpose within the overall layout.
  • configs/README.md – Specific guidance on what belongs in the folder, explicitly mentioning support for confd and consul-template files.
  • pkg/config/ – The conventional location for reusable configuration loading libraries that import settings from /configs.
  • internal/config/ – Private configuration helpers that implement business-specific parsing logic while keeping the /configs directory focused on data files.

Summary

  • Place all configuration templates and default files in the /configs directory at the repository root.
  • Separate JSON, YAML, and template files into logical subdirectories like json/, yaml/, and templates/.
  • Keep configuration loading logic in pkg/config/ or internal/config/ while data files remain in /configs.
  • Use Viper for file-based configuration loading from the predictable /configs path.
  • Document environment variables in configs/.env.example when using envconfig or similar tools.

Frequently Asked Questions

Where should I put environment-specific configuration files in a Go project?

Place environment-specific files like config.dev.json or config.prod.yaml in the /configs directory at your repository root. According to the golang-standards/project-layout, this directory holds all configuration file templates or default configs, keeping them separate from your source code in cmd/ and pkg/.

Can I store sensitive configuration values in the /configs directory?

No. The /configs directory is intended for templates and default configurations only. Store sensitive values in environment variables or secure vaults. You can place a .env.example file in /configs to document required variables without exposing secrets.

What is the difference between /configs and /config in the Go project layout?

The standard specifies /configs (plural) at the repository root for configuration file templates. While some projects use /config (singular) for application-specific configuration code, the golang-standards/project-layout reserves /configs for operational assets and templates like confd or consul-template files.

How do I load configuration files from /configs in my Go application?

Use configuration libraries like Viper that support custom config paths. Point Viper to the configs directory relative to your project root with viper.AddConfigPath("configs"). For environment-variable based configuration, place example files in /configs and load them using envconfig or similar tools.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →