Back to blog

The File Formats of Infrastructure: Compose YAML, .env, systemd Unit Files, and Why Plain Text Matters

A beginner-friendly guide to the plain-text files that define modern services — Docker Compose YAML, .env configuration, and systemd unit files — explaining their syntax, purpose, and why keeping them version-controlled is essential for reliable infrastructure.

Modern infrastructure is not built from binary blobs or proprietary GUIs. It is assembled from plain-text files that describe what should run, how it should be configured, and when it should start. Whether you are spinning up a container stack with Docker Compose, injecting secrets via a .env file, or wiring a service to start at boot with systemd, you are editing text.

This article walks through the three most common formats you will encounter — Compose YAML, .env, and systemd unit files — and explains why treating them as first-class citizens in your repository pays dividends in reproducibility, auditability, and team collaboration.

#1. Docker Compose YAML: Declaring Multi-Container Applications

#1.1 What It Is

docker-compose.yml (or compose.yaml in newer versions) is a YAML document that defines a multi-container application. Each top-level key under services: becomes a container; the file also expresses networks, volumes, and build instructions.

#1.2 Minimal Example

yaml
version: "3.9"
services:
web:
image: nginx:alpine
ports:
- "8080:80"
depends_on:
- api
api:
build: ./api
environment:
- DATABASE_URL=postgres://db:5432/app
volumes:
- ./api:/app

volumes:
db-data:

#1.3 Key Concepts

Concept Purpose
services One entry per container
build / image Source of the container image
ports Host↔container port mapping
depends_on Startup ordering (not health checks)
environment / env_file Runtime configuration
volumes Persistent or bind-mounted storage

#1.4 Why Keep It in Git?

  • Reproducibilitydocker compose up -d on any machine yields the same topology.
  • Reviewability – Pull requests show infra changes alongside code changes.
  • CI/CD friendliness – The same file drives local dev, staging, and production (with overrides).

#2. .env Files: Configuration Without Code Changes

#2.1 The Twelve-Factor App Principle

The Twelve-Factor App methodology states: "Store config in the environment." .env files are a convenient, human-readable way to populate that environment before a process starts.

#2.2 Syntax Rules

  • One KEY=VALUE pair per line.
  • No spaces around = (unless you want them in the value).
  • Lines starting with # are comments.
  • Quoting is optional but recommended for values containing spaces or special characters.

dotenv

#.env.example – committed to repo

APP_ENV=production
DATABASE_URL=postgres://user:pass@db:5432/app
SECRET_KEY="a very long random string"
LOG_LEVEL=info

#2.3 Loading Mechanisms

Tool How It Loads .env
Docker Compose Automatic if file is named .env in project root or via env_file:
dotenv (Node/Python/Ruby) require('dotenv').config() or equivalent
systemd EnvironmentFile=/path/to/.env in unit file
shell set -a; source .env; set +a

#2.4 Security Hygiene

  • Never commit real secrets – commit .env.example with placeholders.
  • Use .gitignore for the actual .env.
  • Rotate secrets via a secrets manager (Vault, AWS Secrets Manager) in production; .env is for local/dev convenience.

#3. systemd Unit Files: The Linux Service Contract

#3.1 What Is a Unit File?

A unit file is an INI-style text file that tells systemd how to start, stop, restart, and relate a service (or timer, mount, socket, etc.). They live in /etc/systemd/system/ (admin) or /usr/lib/systemd/system/ (package).

#3.2 Anatomy of a Service Unit

ini

#/etc/systemd/system/myapp.service

[Unit]
Description=My Application
After=network.target postgresql.service
Requires=postgresql.service

[Service]
Type=simple
User=appuser
WorkingDirectory=/opt/myapp
EnvironmentFile=/opt/myapp/.env
ExecStart=/opt/myapp/venv/bin/gunicorn -w 4 -b 0.0.0.0:8000 wsgi:app
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

#3.3 Important Directives

Section Directive Meaning
[Unit] After= / Requires= Ordering and hard dependencies
[Service] Type= simple, forking, oneshot, notify, …
ExecStart= Command line to launch the daemon
Restart= no, on-success, on-failure, always
EnvironmentFile= Load KEY=VALUE pairs from a file
[Install] WantedBy= Target that pulls this unit in at boot

#3.4 Managing Units

bash

#Reload daemon after editing

sudo systemctl daemon-reload

#Enable + start now

sudo systemctl enable --now myapp

#Inspect logs

journalctl -u myapp -f

#4. Why Plain Text Wins

Benefit Explanation
Diffable git diff shows exactly what changed.
Reviewable Code-review tools work natively.
Portable Runs on any OS with a text editor.
Scriptable sed, awk, yq, envsubst can transform them.
Auditable History = compliance evidence.
No Vendor Lock-in Not tied to a specific UI or API.

Rule of thumb: If a configuration cannot be expressed in a text file committed to version control, treat it as technical debt.

#5. Practical Workflow: From Laptop to Production

  1. Develop – Edit compose.yaml and .env locally.
  2. Commit – Push changes; CI runs docker compose build and tests.
  3. Stage – Deploy to staging with docker compose -f compose.yaml -f compose.staging.yaml up -d.
  4. Promote – On production VMs, systemd unit files pull the same images and read the same .env (populated from secrets manager).
  5. Observejournalctl, docker logs, and metrics dashboards all trace back to the same source files.

#Conclusion

The infrastructure that powers modern applications lives in three humble plain-text formats:

  • Compose YAML describes what runs and how containers relate.
  • .env files inject configuration without baking it into images.
  • systemd unit files bind services to the host lifecycle with dependency ordering and restart policies.

Mastering these formats — and keeping them under version control — turns fragile, manual setups into reproducible, auditable, and collaborative systems. The next time you SSH into a box to "just tweak a setting," ask yourself: "Can this change live in a text file instead?" If the answer is yes, commit it. Your future self (and your teammates) will thank you.

Share this article