Migrating from Environment Variables¶
Affects the upgrade to 2.3.0
Relevant when you upgrade from a release before 2.3.0, published on 2026-01-24. If your
installation already has a config/configuration.yml, this migration is behind you.
Coming from that far back also means going through the InfluxDB migration when you reach 3.0.0.
Up to 2.3.0 the service was configured through environment variables, a .env file, or Docker
secrets. Since then it reads two YAML files.
What changed¶
| File | Contents | Permissions |
|---|---|---|
configuration.yml |
Everything that is not sensitive | 644 |
secrets.yml |
Passwords, tokens, API keys, site and serial numbers | 600 |
Values in configuration.yml reference entries in secrets.yml through the !secret tag.
The gain is structure, a single place for sensitive values with restrictive permissions, editing without rebuilding a container, and validation of the whole file against the settings model rather than one variable at a time.
Migration paths¶
Automatic, on the next start¶
The easiest path, and the one that covers most installations.
When the service starts and config/configuration.yml is missing or empty, it reads environment
variables from three sources: the process environment, a .env file in the working directory,
and Docker secrets under /run/secrets/.
If it finds any, it validates them, writes both YAML files, and continues starting up. If it finds none, it treats the installation as new, copies the example files in, and exits asking you to edit them.
# First run, with the variables of your old installation still set
export SE2MQTT_MODBUS__HOST=192.168.1.100
export SE2MQTT_MQTT__BROKER=mqtt.example.com
solaredge2mqtt
Unset the variables before the next run. Once configuration.yml exists, it is the only source
the service reads.
Manually, with the CLI tool¶
solaredge2mqtt-migrate performs the same conversion without starting the service, which lets
you preview the result.
# Preview, writes nothing
solaredge2mqtt-migrate --input .env --output-dir config --dry-run
# Convert, backing up any existing YAML files first
solaredge2mqtt-migrate --input .env --output-dir config --backup
| Option | Default | Meaning |
|---|---|---|
--output-dir, -o |
. (current directory) |
Where the YAML files are written |
--dry-run, -d |
off | Print the result instead of writing files |
--backup, -b |
off | Rename existing files to *.yml.backup.<timestamp> first |
--input, -i |
.env |
Path to the dotenv file to read |
The tool reads the same three sources as the automatic migration: the process environment, the
dotenv file, and Docker secrets under /run/secrets/. --input selects the dotenv file, so it
does not have to sit in the current directory:
If the named file does not exist the tool says so and continues with the other two sources.
The process environment wins
A variable set in the environment takes precedence over the same key in the dotenv file. Unset leftovers from the old installation if you want the file to decide.
In Docker¶
Keep the environment variables for one start, then drop them.
# First run: the service migrates into the mounted config directory
docker run -d \
-v $(pwd)/config:/app/config \
-e SE2MQTT_MODBUS__HOST=192.168.1.100 \
-e SE2MQTT_MQTT__BROKER=mqtt.example.com \
ghcr.io/deroetzi/solaredge2mqtt:latest
ls -la config/
# Second run: no -e flags, the YAML files are used
docker run -d \
-v $(pwd)/config:/app/config \
ghcr.io/deroetzi/solaredge2mqtt:latest
With Compose, keep the environment section for the first docker compose up, then reduce
it to what the container itself needs:
cat > .env <<'ENV'
SE2MQTT_MODBUS__HOST=192.168.1.100
SE2MQTT_MQTT__BROKER=mqtt.example.com
SE2MQTT_MQTT__PASSWORD=secret123
ENV
docker run --rm \
-v $(pwd)/.env:/app/.env:ro \
-v $(pwd)/config:/app/config \
ghcr.io/deroetzi/solaredge2mqtt:latest \
solaredge2mqtt-migrate --output-dir config --backup
rm .env
With Compose:
For a small setup it can be quicker to start from the examples than to migrate. The steps are in Installation.
How names map¶
Every variable was prefixed with SE2MQTT_. A double underscore separated a section from a
field and becomes one level of nesting.
SE2MQTT_INTERVAL=5
SE2MQTT_LOGGING_LEVEL=INFO
SE2MQTT_MODBUS__HOST=192.168.1.100
SE2MQTT_MODBUS__PORT=1502
Lists¶
A digit appended directly to the field name, not separated by another double underscore, becomes a list index:
Writing METER__0 instead produces a dictionary with the key "0", which the settings model
rejects.
Secrets¶
Sensitive values are split into secrets.yml and referenced by a !secret tag, with the name
built from the section and the field:
# configuration.yml
mqtt:
password: !secret mqtt_password
monitoring:
password: !secret monitoring_password
# secrets.yml, written with 600 permissions
mqtt_password: "secret123"
monitoring_password: "api_secret"
The split is not driven by the field name. A field moves into secrets.yml when the settings
model declares it as a Pydantic SecretStr, which today means mqtt.password,
mqtt.keyfile_password, monitoring.site_id, monitoring.password, wallbox.password,
wallbox.serial and weather.api_key.
Afterwards¶
- Check the files.
ls -la config/should showconfiguration.ymlat644andsecrets.ymlat600. - Check that every secret resolves. Compare
grep "!secret" config/configuration.ymlagainst the keys insecrets.yml. - Start and read the log. A successful start logs
Loaded secrets from ...andLoaded configuration from .... - Remove the old configuration. Delete or rename
.env, and strip theenvironmententries and any Docker secrets from your Compose file, keeping container-level variables such asTZ. - Back it up.
tar -czf config-backup-$(date +%Y%m%d).tar.gz config/, or commitconfiguration.ymlto git withsecrets.ymlexcluded.
Troubleshooting¶
The service cannot find the configuration¶
It starts a migration you did not expect, or reports a missing file.
ls -la config/to confirm the directory and its contents.chmod 644 config/configuration.ymlandchmod 600 config/secrets.yml.- For Docker, confirm the mount:
docker inspect <container> | grep -A 10 Mounts.
A secret reference does not resolve¶
- Compare both files. Names are case-sensitive,
!secret mqtt_passwordmatchesmqtt_passwordand nothing else. - Quote values containing YAML special characters:
mqtt_password: "secret@123!".
A value is rejected as the wrong type¶
The migration aborts with a validation error naming a field, for instance that port is not a
valid integer.
Quotation marks in .env are part of the value, they are not stripped. PORT="1502" reaches the
settings model as a string that still contains the quotes. Write values unquoted:
Everything that passes validation is written from the validated model, so the generated YAML carries real types.
The migration tool fails¶
pip install --upgrade solaredge2mqtt.- Check the
.envformat: oneKEY=VALUEper line, no spaces around=, no quotes. - Check that the output directory is writable:
mkdir -p config && chmod 755 config.
The container cannot write the files¶
- Create the directory before the first start:
mkdir -p config. - Give the container ownership:
sudo chown -R 1000:1000 config/. - On SELinux systems, add
:Zto the mount.
More on container permissions is on the Docker page.
Running several environments¶
solaredge2mqtt --config-dir /etc/solaredge2mqtt/production
solaredge2mqtt --config-dir /etc/solaredge2mqtt/testing
secrets.yml is plain YAML with no schema of its own, so it can be generated at deploy time from
Vault, AWS Secrets Manager or anything else that writes a file. Keep configuration.yml in
version control and render only the secrets per environment.