# How to Organize Initialization and System Config Files in Go Projects

> Organize Go project initialization and system config files effectively. Use init/ for scripts and configs/ for templates to separate deployment from runtime settings.

- Repository: [golang-standards/project-layout](https://github.com/golang-standards/project-layout)
- Tags: best-practices
- Published: 2026-03-06

---

**Place system-level init scripts in the `init/` directory and application configuration templates in the `configs/` directory to maintain clean separation between deployment artifacts and runtime settings.**

The `golang-standards/project-layout` repository establishes conventions for organizing Go projects at scale. When managing initialization and system configuration files in Go, following these directory standards ensures that operating system integrations remain decoupled from application source code while keeping default configurations version-controlled and accessible.

## Understanding the Canonical Directory Structure

According to the project layout standard, two top-level directories serve distinct purposes for deployment-time artifacts. These locations are documented in [`init/README.md`](https://github.com/golang-standards/project-layout/blob/main/init/README.md) and [`configs/README.md`](https://github.com/golang-standards/project-layout/blob/main/configs/README.md) respectively.

### The init/ Directory

The `init/` directory houses **system-level initialization scripts** and process manager configurations. This includes systemd unit files, upstart scripts, sysvinit scripts, and configurations for runit or supervisord. These files are consumed by the host operating system rather than the Go binary itself, which is why they reside outside the `cmd/` and `internal/` source trees. Placing these artifacts in `init/` makes it straightforward for packaging scripts to locate and install them into system directories like `/etc/systemd/system/` or `/etc/supervisor/conf.d/`.

### The configs/ Directory

The `configs/` directory stores **default configuration file templates** such as those used by `confd` or `consul-template`. Unlike the files in `init/`, these templates represent application-level data that the Go binary reads at runtime via libraries like Viper or standard library functions such as `os.Getenv`. Keeping templates in this dedicated folder simplifies version control of defaults and enables embedding directly into binaries using the `//go:embed` directive.

## Practical Implementation Strategies

### Embedding Default Configurations with go:embed

Since Go 1.16, you can embed configuration templates directly into your binary to ensure it ships with sane defaults. Store the template in `configs/` and reference it from an internal package:

```go
package config

import (
	"bytes"
	_ "embed"
	"text/template"

	"github.com/spf13/viper"
)

//go:embed ../configs/config.yaml.tmpl
var defaultYAML []byte

// Load reads configuration from a file path; if the file does not exist it falls back
// to the embedded template rendered with runtime values.
func Load(path string) (*viper.Viper, error) {
	v := viper.New()
	v.SetConfigFile(path)
	if err := v.ReadInConfig(); err == nil {
		return v, nil
	}
	t, err := template.New("default").Parse(string(defaultYAML))
	if err != nil {
		return nil, err
	}
	var buf bytes.Buffer
	err = t.Execute(&buf, map[string]string{
		"Port": "8080",
	})
	if err != nil {
		return nil, err
	}
	v.SetConfigType("yaml")
	if err := v.ReadConfig(&buf); err != nil {
		return nil, err
	}
	return v, nil
}

```

This approach guarantees the binary can start even when external configuration files are missing, while still allowing operators to override settings via files in `/etc/` or environment variables.

### Creating Systemd Unit Files

Place systemd service definitions in `init/` to keep OS-specific wiring separate from source code. A typical unit file at `init/myapp.service` looks like this:

```ini
[Unit]
Description=My Go Application
After=network-online.target
Wants=network-online.target

[Service]
ExecStart=/usr/local/bin/myapp --config /etc/myapp/config.yaml
Restart=on-failure
EnvironmentFile=-/etc/myapp/env

[Install]
WantedBy=multi-user.target

```

Include this file in your packaging logic so deployment scripts can execute `systemctl enable myapp` on the target host.

### Configuring Process Managers

For environments using supervisord, store program definitions in [`init/myapp.conf`](https://github.com/golang-standards/project-layout/blob/main/init/myapp.conf):

```ini
[program:myapp]
command=/usr/local/bin/myapp --config /etc/myapp/config.yaml
autorestart=true
stderr_logfile=/var/log/myapp.err.log
stdout_logfile=/var/log/myapp.out.log
environment=ENVIRONMENT="production",DEBUG="false"

```

This configuration leverages the `configs/` directory for the application config file while the init script manages process lifecycle concerns like log rotation and auto-restart policies.

## Deployment Workflow Integration

A typical CI/CD pipeline integrating these directories follows four stages:

1. **Package**: Copy `init/*.service` or `init/*.conf` into the appropriate system directories during the packaging phase (e.g., within a `dpkg` or `rpm` spec file).
2. **Render Config**: If using templating tools like `consul-template`, place the source template in `configs/` and render it to [`/etc/myapp/config.yaml`](https://github.com/golang-standards/project-layout/blob/main//etc/myapp/config.yaml) during deployment.
3. **Install Binary**: Build the binary with `go build -o /usr/local/bin/myapp ./cmd/myapp`, ensuring embedded defaults are included via `//go:embed`.
4. **Enable Service**: Execute `systemctl enable --now myapp.service` or `supervisorctl reread && supervisorctl update` to activate the service using the files from `init/`.

## Summary

- **Separate concerns** by placing OS init scripts in `init/` and application config templates in `configs/`.
- **Embed defaults** using Go's `//go:embed` directive to create self-contained binaries that reference files from `configs/`.
- **Version control deployment artifacts** alongside source code to enable peer review and rollback capabilities for systemd units and process manager configs.
- **Support multiple deployment strategies** (bare-metal, containers, Kubernetes) by maintaining both directories in a single source tree.

## Frequently Asked Questions

### What's the difference between init/ and configs/ in Go projects?

The `init/` directory contains deployment-time artifacts like systemd units and supervisord configs that tell the operating system how to manage the process lifecycle. The `configs/` directory contains runtime data templates that the Go application reads via libraries like Viper. This separation ensures OS-specific wiring never mixes with application-level configuration logic.

### How do I embed configuration files in a Go binary?

Use the `//go:embed` directive in a package file to reference templates stored in `configs/`. For example, `//go:embed ../configs/config.yaml.tmpl` imports the template as a byte slice that you can parse with `text/template` and load into Viper. This technique, available since Go 1.16, guarantees your binary ships with working defaults.

### Where should I place systemd service files in a Go project?

Place systemd unit files in the `init/` directory at the repository root, as defined by the golang-standards/project-layout convention. This location keeps service definitions separate from source code while making them easily discoverable by packaging scripts that install files to `/etc/systemd/system/` during deployment.

### Can I use these directories with containerized deployments?

Yes. While containers often use entrypoint scripts rather than systemd, the `configs/` directory remains valuable for storing default configuration templates that you copy into container images. The `init/` directory can contain alternative process supervisor configs or reference material for operators translating systemd units into Kubernetes manifests or Docker Compose files.