.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 600means 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: .envloads every variable from.envinto 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
.envanddocker 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.ymland.envover and the stack is wired up.
3. Clean and Readable Configs
- No more massive
docker-compose.ymlfiles 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?
.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?
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?
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?
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.