Repository navigation
Docker
Advanced — Get started does not need any of this. Reach for a container when you want the controller running unattended on a machine you do not develop on: a Raspberry Pi, a home server, a NAS. The payoff is that you never build on the target machine — CI publishes every platform to GHCR and the target only pulls, which matters on a Pi where a local .NET build is slow enough to be impractical.
Everything below assumes Docker Engine with the Compose v2 plugin, and the same reachability check Get started opens with:
docker --version
docker compose version
nc -vz 192.168.2.10 502 # inverter
nc -vz 192.168.2.6 502 # EV chargerOne name covers every platform. ghcr.io/mpospisil/solax-controller is a multi-platform manifest
list, so the same tag resolves to the correct image on a Raspberry Pi, an x64 Linux server or a
Windows host — you name a version, never an architecture.
| Tag | What it is | Use it for |
|---|---|---|
:latest |
Manifest list, built from main
|
Tracking development |
:1.0.0 :1.0 :1
|
Manifest list, from a v* release tag |
Normal production use |
:sha-abc1234 |
Manifest list, every build | Rollback to a known-good build |
:<version>-linux-arm64 |
Single platform | Pinning one architecture |
:<version>-linux-amd64 |
Single platform | Pinning one architecture |
:<version>-nanoserver-ltsc2022 |
Single platform | Pinning the Windows image |
Prefer a manifest-list tag. Reach for a platform-suffixed one only when you deliberately want to stop a host resolving its own architecture.
The quickest useful thing: the controller polling and logging, serving its own web UI, and writing nothing to your hardware. Create the host directories first and hand them to the image's non-root user, uid 1654 — the container cannot write to them otherwise.
mkdir -p /opt/solax/{data,logs}
sudo chown -R 1654:1654 /opt/solax/data /opt/solax/logs
docker run -d --name solax-controller --restart on-failure \
-e TZ=Europe/Prague \
-e Solax__Inverter__Host=192.168.2.10 \
-e Solax__EvCharger__Host=192.168.2.6 \
-p 8090:8090 \
-v /opt/solax/data:/app/data \
-v /opt/solax/logs:/app/logs \
ghcr.io/mpospisil/solax-controller:latestThe two inverter addresses are the only settings here that have to be yours. Open
http://localhost:8090 — or the host's LAN address — and the dashboard, controls, session
history and forecast plan are all there, with no login. Nothing enabled it: the UI is on by default,
and -p 8090:8090 is only what makes the container's port reachable from outside it.
Every setting in appsettings.json can be overridden this way, : written as __ — see
Configuration. On a Raspberry Pi or a Windows host there is one extra thing to get
right first: Platforms.
The UI above has no login — anyone who can reach the port gets the dashboard and every control on it, including the ones that write to the inverter and charger. On a household LAN that is usually fine, and it is what lets the container above work with nothing configured. Add a password if the network has guests on it, or if the port is reachable from beyond it.
It is one environment variable. Generate the hash first — no config, no listening socket, it prints and exits — then pass the hash, never the password:
docker run --rm ghcr.io/mpospisil/solax-controller:latest hash-password '<your password>'
docker run -d --name solax-controller --restart on-failure \
-e TZ=Europe/Prague \
-e Solax__Inverter__Host=192.168.2.10 \
-e Solax__EvCharger__Host=192.168.2.6 \
-e Web__PasswordHash='<the hash>' \
-p 8090:8090 \
-v /opt/solax/data:/app/data \
-v /opt/solax/logs:/app/logs \
ghcr.io/mpospisil/solax-controller:latestEvery page now redirects to a login form. There is no second switch: the hash's presence is what
requires the login, so a configured password can't end up unenforced. Web__RequireAuthentication
can override that in either direction and is rarely worth setting — see
Configuration for the full table.
-e Web__Enabled=false makes the controller headless again: no socket is bound inside the container
at all, and -p 8090:8090 would then publish a port whose connections are refused. Drop both if you
want the MQTT-only deployment.
The first line of the log is the running build — for example 1.0.0 (31bf347). A version of
0.0.0-dev with no commit hash means a local build rather than anything CI published.
docker logs -f solax-controller
# memory limits are actually being applied (Linux)
docker stats --no-streamLeave the charge mode on Off until the telemetry looks right — that is the recommended way to
confirm you are reading the correct registers before anything writes. The log says which state the UI
is in: Web UI enabled; listening on port 8090, login not required. With Web__Enabled=false there
is no such line, nothing on 8090 and no socket in ss -ltnp; that is the expected state, not a
fault.
Ask the service to stop rather than killing it. The Health page in the web UI has a Stop
service button (Home Assistant gets one too), and docker stop solax-controller does the same
thing. All three run the same graceful shutdown: the charger's current setpoint is returned to the
pause current, the open charging session is closed and written, the session store is flushed.
docker kill, docker rm -f and kill -9 skip all of it. Little is lost from disk — SQLite is
crash-safe here and the log is written unbuffered — but nothing revokes the charging current, so a
car keeps drawing at whatever the controller last wrote until something else changes it.
--restart on-failure above is what makes a stop stay a stop. The controller sets its exit code
deliberately so the policy can tell the two cases apart:
| How the run ended | Exit code | What Docker does |
|---|---|---|
| Stop service in the UI or Home Assistant | 0 |
leaves it down until you start it |
docker stop |
— | leaves it down (a manual stop always wins) |
Reboot, daemon restart, docker restart
|
143 |
starts it again |
| Crash, OOM kill, power cut | non-zero | starts it again |
With --restart unless-stopped instead, the Stop button becomes a restart button — the container
comes back a second later. Start it again with docker start solax-controller.
Every run that ends properly says so on its last line:
SolaX Local Controller stopped cleanly at the request of Web UI. Exiting with code 0: …
SolaX Local Controller stopped cleanly after a termination signal. Exiting with code 143: …
A log that ends without one of those is a run that died.
A single docker run is fine for a first look, but the repository ships a Compose deployment that
keeps configuration in one .env file on the target and can start Home Assistant and an MQTT broker
alongside the controller. That — and the deploy.sh / deploy-controller-only.sh split that decides
which containers actually run — is on the Deployment page.
Previous: Configuration · Next: Platforms
Runs entirely on your LAN · Full configuration reference in README.md · Pi preparation and backup in deploy/README.md
- Home
- Get started — build & run
- Configuration
- Solcast
- Weather
- Troubleshooting
Advanced
- Docker
- Platforms
- Deployment — Pi, two workflows
- MQTT
Images: ghcr.io/mpospisil/solax-controller