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
/configscan 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/orpkg/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 asconfig.dev.jsonandconfig.prod.json.yaml/– Stores YAML equivalents likeconfig.yamlfor environment-specific settings.templates/– Holds text/template files used by tools likeconfdfor dynamic configuration generation.example/– Provides sample config files for new developers to copy and customize.scripts/– Includes helper scripts such asrender.shthat 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/configspurpose within the overall layout.configs/README.md– Specific guidance on what belongs in the folder, explicitly mentioning support forconfdandconsul-templatefiles.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/configsdirectory focused on data files.
Summary
- Place all configuration templates and default files in the
/configsdirectory at the repository root. - Separate JSON, YAML, and template files into logical subdirectories like
json/,yaml/, andtemplates/. - Keep configuration loading logic in
pkg/config/orinternal/config/while data files remain in/configs. - Use Viper for file-based configuration loading from the predictable
/configspath. - Document environment variables in
configs/.env.examplewhen 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →