DiyMediaServer
Featured image of post Master the Basics - Docker Env Files

Master the Basics - Docker Env Files

Your Secret Weapon for Managing Docker Containers

💭
TL;DR: A .env file pulls every shared variable (PUID, PGID, TZ, ports, paths) out of your docker-compose.yml into one editable file. Change one value, restart the stack, and every Arr container picks it up.

Why You Need a .env File

Setting up a media server with Radarr, Sonarr, Prowlarr, and SABnzbd gets messy fast. If you’ve ever dug through your docker-compose.yml to change one setting across four services, you know the pain.

Hardcoding paths, user IDs, and timezone values across multiple containers makes updates a nightmare. Want to change your downloads directory? That’s at least four places you’ll need to edit. Move your media to a new drive? Worse.

That’s where the .env file comes in.

A .env file centralizes every shared variable into a single file. Update one value, restart the stack, and every container follows. Clean, modular, portable. I run this exact setup on my own Proxmox host with the LinuxServer.io Radarr, Sonarr, Prowlarr, and SABnzbd images, and the day I moved my media drive I edited two lines and was done.

Here’s how to create, wire up, and lock down a .env file for your Arr stack.

Step 1: Creating the .env File

Drop the .env file in the same directory as your docker-compose.yml. In previous posts the compose file lives in /docker, so that’s where this one goes too.

cd /docker
nano .env

The .env file is hidden. Any file starting with . won’t show up in a normal ls (use ls -a to see it).

Paste this in:

# User and Group ID (Prevents permission issues)
# Main user ID
PUID=1000
# Our media group:
PGID=1001

# Timezone (Ensures correct scheduling and logs)
TZ=America/Denver

# Define Ports (Ports for each container are defined here)
RADARR_PORT=7878
SONARR_PORT=8989
PROWLARR_PORT=9696
SABNZBD_PORT=8080

# Data Directories (Keeps storage paths centralized)
CONFIG_PATH=/docker
DOWNLOADS_PATH=/media/Downloads
MEDIA_PATH=/media

Breaking Down the Variables

  • PUID & PGID - Which user the container runs as. If you’ve ever had files created with the wrong ownership, this fixes it.
  • TZ - Time zone for logs and scheduled tasks (downloads, library scans).
  • *_PORT - The host port each container binds to. Change one number here instead of hunting through compose.
  • CONFIG_PATH - Where each app stores its settings. Keeping all configs in one parent folder makes backups and migrations trivial.
  • DOWNLOADS_PATH - Shared download directory for SABnzbd (and any other download client).
  • MEDIA_PATH - Where finished movies and TV shows live. Jellyfin, Plex, and Emby read from here.

One file, one place to edit. That’s the whole point.

Important Note

Make sure CONFIG_PATH exists and has the right permissions before you bring the stack up:

sudo mkdir /docker
sudo chown -R $USER:media /docker
sudo chmod -R 770 /docker

Step 2: Securing the .env File

This file holds configuration values and, if you extend it, API keys. Lock it down:

chmod 600 /docker/.env

Why This Is Important

  • chmod 600 means only the owner can read or write the file.
  • Stops accidental edits and blocks anyone else on the box from reading it. Important the day you add a real API key.

Step 3: Using the .env File in docker-compose.yml

Now wire docker-compose.yml to pull values from .env.

services:
  radarr:
    image: lscr.io/linuxserver/radarr
    container_name: radarr
    env_file: .env
    ports:
      - ${RADARR_PORT}:7878
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
    volumes:
      - ${CONFIG_PATH}/radarr:/config
      - ${DOWNLOADS_PATH}:/downloads
      - ${MEDIA_PATH}/Movies:/movies
    restart: unless-stopped

  sonarr:
    image: lscr.io/linuxserver/sonarr
    container_name: sonarr
    env_file: .env
    ports:
      - ${SONARR_PORT}:8989
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
    volumes:
      - ${CONFIG_PATH}/sonarr:/config
      - ${DOWNLOADS_PATH}:/downloads
      - ${MEDIA_PATH}/Shows:/tv
    restart: unless-stopped

  prowlarr:
    image: lscr.io/linuxserver/prowlarr
    container_name: prowlarr
    env_file: .env
    ports:
      - ${PROWLARR_PORT}:9696
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
    volumes:
      - ${CONFIG_PATH}/prowlarr:/config
    restart: unless-stopped

  sabnzbd:
    image: lscr.io/linuxserver/sabnzbd
    container_name: sabnzbd
    env_file: .env
    ports:
      - ${SABNZBD_PORT}:8080
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
    volumes:
      - ${CONFIG_PATH}/sabnzbd:/config
      - ${DOWNLOADS_PATH}:/downloads
    restart: unless-stopped

How This Works

  • env_file: .env loads every variable from .env into the container’s environment.
  • ${CONFIG_PATH}, ${DOWNLOADS_PATH}, and ${MEDIA_PATH} replace the hardcoded paths at compose time.
  • Moving your media to a new drive? Update .env and docker compose up -d. Done.
  • ${PUID} and ${PGID} swap the user and group the containers run as.
  • ${TZ} changes the time zone everywhere at once.

Step 4: Testing and Validating Your Setup

Bring the stack up:

docker compose up -d

Check the logs to confirm the variables landed:

docker logs radarr

You should see the right paths and time zone in the startup output.

You can also exec into the container and read the env directly:

docker exec -it radarr env | grep CONFIG_PATH

You should see:

CONFIG_PATH=/docker

If that matches what’s in your .env, you’re good.

Step 5: Why This Matters in the Long Run

1. Easy Updates

  • Moving media to a new drive? Edit .env, restart the stack, every container follows.
  • Changing the time zone? One edit, not five.

2. Portability

  • New server? Copy docker-compose.yml and .env over and the stack is wired up.

3. Clean and Readable Configs

  • No more massive docker-compose.yml files stuffed with hardcoded paths.

4. Security and Best Practices

  • API keys and passwords stay out of docker-compose.yml, where they’re more likely to end up in a git push you regret.

Taking It Further: Advanced .env Use Cases

Want more out of .env? A couple of patterns worth knowing.

Use .env for API Keys (Carefully)

For services like NZBHydra or a third-party indexer, drop the API key into .env:

NZBHYDRA_API_KEY=yourapikeyhere

Then reference it in docker-compose.yml:

environment:
  - API_KEY=${NZBHYDRA_API_KEY}

Caution: never commit .env to GitHub or any public repo. Add it to .gitignore the moment you create it. While you’re there, add .env.* too so backup copies like .env.bak don’t sneak in.

One file. Easy updates. No headaches.

Wrapping Up

Once the stack is up, you can hit each container from your browser:

  • Sonarr: http://your-server-ip:8989
  • Radarr: http://your-server-ip:7878
  • Prowlarr: http://your-server-ip:9696
  • SABnzbd: http://your-server-ip:8080

Need your server’s IP? Run:

ip a | grep inet

A .env file is one of the highest-leverage habits in your Docker toolbox. It keeps your Arr stack modular, easy to manage, and portable.

Set it up now and stop hardcoding settings.

Frequently Asked Questions

➤ Where does Docker Compose look for the `.env` file?
Docker Compose loads .env from the same directory as the docker-compose.yml file you’re running. Run docker compose from /docker and it picks up /docker/.env automatically. If you keep your compose file elsewhere, pass --env-file /path/to/.env on the command line.
➤ Does `env_file: .env` do the same thing as variable substitution?
No. env_file: injects variables into the container’s runtime environment so the app inside sees them. ${VAR} substitution happens at compose parse time and rewrites the YAML before the container starts. Use substitution for ports and volume paths, and env_file for variables your app reads at runtime.
➤ How do I check which variables a running container actually received?
Run docker exec -it <container> env to dump every variable inside the container. Pipe it through grep to find a specific one, for example docker exec -it radarr env | grep TZ. If a variable is missing, the .env file wasn’t found or the key was misspelled in compose.
➤ Is it safe to put API keys in a `.env` file?
Safer than putting them in docker-compose.yml, but .env is still plain text on disk. Lock the file with chmod 600, add .env and .env.* to .gitignore, and never paste it into a screenshot or pastebin. For higher-stakes secrets, look at Docker secrets or a dedicated secrets manager.

Was this useful?

Last updated on May 20, 2026 06:56 MDT