A lightweight .NET 9 background service that automatically keeps your local .env files in sync with their corresponding versions hosted in remote Git repositories (e.g., GitHub).
- Features
- Requirements
- Configuration
- Deployment
- How It Works
- Behavior Details
- Troubleshooting
- License
- π Automatically detects outdated local
.envfiles by comparing variable names with the remote version - β Adds missing environment variables found in the repository
.envto the local file - π¬ Preserves and syncs comments associated with each variable
- π§© Supports multi-line variable values (e.g., JSON strings)
- πΎ Optional backup of the original
.envfile before any update (.env.bak_TIMESTAMP) - π Supports multiple services, each with its own repository URL and local file path
- π Automatically converts standard GitHub URLs to raw content URLs
- π³ Docker-ready with simple volume-based configuration
- .NET 9 SDK (for local development/run)
- Docker and Docker Compose (for containerized deployment)
The service is configured entirely via a config.json file located in the application's root directory. This file defines which services to monitor, how often to check for updates, and general behavior settings.
| Field | Type | Description | Default |
|---|---|---|---|
checkInterval |
int |
Interval in seconds between each sync check | 3600 |
saveBackupFile |
bool |
If true, a .env.bak_TIMESTAMP backup is created before any update |
true |
timezone |
string |
Timezone used for logging timestamps (e.g., UTC, America/New_York, Europe/Rome) |
"UTC" |
services |
array |
List of service objects to monitor (see below) | [] |
logLevel |
string |
Logging verbosity level (Trace, Debug, Information, Warning, Error, Critical). For Information, Warning, and Critical, short forms are also accepted (info, warn, crit). |
info |
Each entry in the services array represents a single .env file to keep in sync:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string |
β | A friendly name to identify the service in logs |
repositoryEnvUrl |
string |
β | The URL pointing to the remote .env file in the Git repository |
localEnvPath |
string |
β | The absolute path to the local .env file to keep in sync |
Note
Both standard GitHub URLs (https://github.com/user/repo/blob/main/.env) and raw URLs (https://raw.githubusercontent.com/user/repo/refs/heads/main/.env) are supported. Standard URLs are automatically converted to raw format at runtime.
Below is a complete config.json example that monitors three different services:
{
"services":
[
{
"name": "AuthService",
"repositoryEnvUrl": "https://raw.githubusercontent.com/myorg/auth-service/refs/heads/main/.env-sample",
"localEnvPath": "/app/envs/auth-service/.env"
},
{
"name": "PaymentGateway",
"repositoryEnvUrl": "https://github.com/myorg/payment-gateway/blob/main/.env-sample",
"localEnvPath": "/app/envs/payment-gateway/.env"
},
{
"name": "NotificationService",
"repositoryEnvUrl": "https://raw.githubusercontent.com/myorg/notifications/refs/heads/develop/.env-sample",
"localEnvPath": "/app/envs/notifications/.env"
}
],
"checkInterval": 1800,
"saveBackupFile": true,
"timezone": "Europe/Rome",
"logLevel": "info"
}
Important
When running inside Docker, the localEnvPath values must reference paths inside the container. These paths must be mapped to the actual host directories via Docker volumes. See the Deployment section for details.
The recommended way to deploy EnvAutoUpdater is via Docker Compose. Create a docker-compose.yml file:
services:
env-auto-updater:
image: krystall0/envautoupdater:latest
container_name: env-auto-updater
restart: unless-stopped
volumes:
# Mount the configuration file (read-only)
- ./config.json:/app/config.json:ro
# Mount the local .env directories for each monitored service.
# The container needs read/write access to update the .env files.
- /home/user/projects/auth-service:/app/envs/auth-service
- /home/user/projects/payment-gateway:/app/envs/payment-gateway
- /home/user/projects/notifications:/app/envs/notifications
Then, start the service:
docker-compose up -d
This will download the latest image, create the container, and start the EnvAutoUpdater service in detached mode. The logs can be viewed with:
docker-compose logs env-auto-updater -f
Ensure that the host directories (e.g., /home/user/projects/auth-service) exist and contain the .env files you want to monitor. The container will map these directories to /app/envs/... paths.
Caution
If a volume is not correctly mapped, the container will not be able to read or write the .env file. The service will log an error and skip that service until the next check cycle.
Tip
Mount config.json as read-only (:ro) since the service only reads it. Do not mount .env directories as read-only β the service needs write access to update them.
For development or testing, you can run the service directly on your machine (assuming .NET 9 SDK is installed):
- Clone the repository:
git clone https://github.com/kRYstall9/EnvAutoUpdater.git
cd EnvAutoUpdater
-
Configure the service:
- Edit
config.jsonto your needs.
- Edit
-
Run the service:
dotnet run
- Read configuration β loads the list of services to monitor from
config.jsonat each iteration (the config can be changed without restarting the container; its updated content will be read at the next iteration). - Read local
.envβ reads the local file line by line viaReadLocalEnvFile. - Fetch remote
.envβ downloads the latest.envfrom the configured repository URL viaReadUpdatedEnvFileFromRepository. - Extract variable names β parses both files via
ReadEnvVariablesto extract environment variable names (lines containing=). - Compare β checks via
IsFileAlreadyUpdatedif all remote variable names are already present in the local file. - Update β if differences are found,
UpdateEnvFile:- Appends a clearly marked section at the bottom of the local file.
- Adds any missing variables (commented out, preserving their original values and associated comments).
- Updates inline comments for variables already present locally.
- Backup (optional) β saves a
.env.bak_TIMESTAMPcopy before writing changes. - Wait β sleeps for
checkIntervalseconds, then repeats from step 1.
- Lines that are empty, do not contain
=, or have spaces in the variable name are skipped. - Commented-out variables (prefixed with
#) are recognized and their names are extracted by stripping the#prefix. - Duplicate variable names across the file are automatically deduplicated.
Variables whose values span multiple lines (e.g., embedded JSON strings) are fully captured. The parser continues reading lines until it encounters an empty line, a comment, or a new variable assignment.
New variables found in the remote .env are appended commented out at the bottom of the local file, under a clearly identifiable marker section. This ensures your existing configuration is never overwritten β you review and uncomment the new variables manually.
If a variable already exists locally but its associated comments in the repository have changed, EnvAutoUpdater will:
- Remove the outdated comments above the existing variable line.
- Insert the updated comments from the repository in their place.
- If
saveBackupFileis enabled, a backup of the original.envfile is saved as.env.bak_TIMESTAMPbefore any updates are applied. The timestamp format isyyyyMMddTHHmmss(e.g.,.env.bak_20230101T120000).
| Problem | Possible Cause | Solution |
|---|---|---|
| Service skips a service entry | localEnvPath points to a nonexistent file or unmapped volume |
Verify the volume mapping in docker-compose.yml and ensure the .env file exists |
| No updates detected | Local file already contains all remote variables | Check logs for detailed comparison output |
| Permission denied errors | Docker container lacks write access to the mounted volume | Ensure the volume is not mounted as read-only (:ro) for .env directories |
| GitHub rate limiting | Too many requests to raw.githubusercontent.com | Increase checkInterval to reduce request frequency |
| Wrong timestamps in logs | Incorrect timezone configuration | Set timezone in config.json to your local timezone (e.g., Europe/Rome) |
This project is licensed under the MIT License. See the LICENSE file for details.