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?
- Reproducibility –
docker compose up -don 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=VALUEpair 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.examplewith placeholders. - Use
.gitignorefor the actual.env. - Rotate secrets via a secrets manager (Vault, AWS Secrets Manager) in production;
.envis 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
- Develop – Edit
compose.yamland.envlocally. - Commit – Push changes; CI runs
docker compose buildand tests. - Stage – Deploy to staging with
docker compose -f compose.yaml -f compose.staging.yaml up -d. - Promote – On production VMs, systemd unit files pull the same images and read the same
.env(populated from secrets manager). - Observe –
journalctl,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.
.envfiles 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.
