Skip to content

Installation

LCM is a single binary with an embedded frontend. There are three ways to run it. For production, prefer the .deb package (systemd service) or Docker.

  • A host for LCM: Debian 12/13 or Ubuntu 22.04/24.04 (amd64 or arm64). Other Linux distributions generally work but are not part of our testing.
  • SSH access (password or key) to the servers you want to manage - LCM creates a dedicated service user there during onboarding.
  • Optionally Trivy on the LCM host for the CVE scan. Without it, the feature disables itself cleanly. bubblewrap is installed alongside it - the sandbox LCM runs the scanner in (see CVE scan).
Section titled “Option 1: Debian/Ubuntu package (recommended)”

Easiest via the TechEve APT repository - set it up once, then install and keep it current with apt upgrade:

Terminal window
# 1. Set up the repository (including the signing key)
# 0. Prerequisites - minimal/cloud images do not ship curl
sudo apt-get install -y curl ca-certificates
curl -fsSL https://repo.techeve.de/setup.sh | sudo sh
# 2. Install LCM
sudo apt install lcm
# 3. Update later (together with the rest of the system)
sudo apt update && sudo apt upgrade

setup.sh adds the package source and GPG key (Debian/Ubuntu, amd64 & arm64); lcm is then a normal apt package, so updates arrive automatically with the system.

Alternatively, without the repository - download a single package from the releases (lcm_<version>_amd64.deb or ..._arm64.deb; check the architecture with dpkg --print-architecture) and install it:

Terminal window
sudo apt install ./lcm_<version>_amd64.deb

Both paths set LCM up as an unprivileged systemd service (autostart, HTTPS).

When a newer version is available in the configured package channel, a banner appears at the top with an Update now button. LCM then installs its own package. Three things matter here:

  • A backup is taken first. Before installing, LCM creates a system backup - and if that fails, it does not update. Whoever updates their own management system has no second one to help them if it goes wrong. The banner names the file created; without a stored backup passphrase the run aborts with exactly that reason.
  • Running jobs are waited out. The banner names what it is waiting for; the update starts only once no job is running. After 30 minutes the wait is cancelled with a message.
  • LCM restarts in the process. The apt run lives in its own systemd unit (lcm-self-update) and therefore survives the restart; its log is in journalctl -u lcm-self-update. The interface notices the version change on its own and reloads.

The button only shows where it can do something: a Debian-package install whose host is registered as an apt server. Otherwise the banner states the reason. In a container you replace the image instead.

The info dialog (click the copyright notice at the bottom) also offers Check now: it queries the package channel right away and shows the result in the banner - including when LCM is already up to date.

The CVE scan requires Trivy. It is in no standard package source of Debian or Ubuntu and must therefore be set up from the vendor’s repository - LCM runs fine without Trivy, but the CVE scan stays disabled:

Terminal window
# Prerequisite - gnupg is missing on minimal/cloud images
sudo apt-get install -y gnupg
wget -qO- https://get.trivy.dev/deb/public.key \
| sudo gpg --dearmor -o /usr/share/keyrings/trivy.gpg
echo "deb [signed-by=/usr/share/keyrings/trivy.gpg] https://get.trivy.dev/deb generic main" \
| sudo tee /etc/apt/sources.list.d/trivy.list
sudo apt update && sudo apt install trivy

If LCM runs on the same host you can also do it with one click: server detail of the LCM host → set up Trivy.

PathContents
/usr/bin/lcmthe program (binary with embedded web UI)
/lib/systemd/system/lcm.servicethe hardened systemd unit
/etc/lcm/config.jsonconfiguration (created with a random JWT secret)
/var/lib/lcm/state: encrypted DB, master key, TLS certificate, backups
/var/lib/lcm/logs/lcm.logpersistent, rotating log file (see below)

The service runs as its own system user lcm without root privileges.

Terminal window
systemctl status lcm # status
journalctl -u lcm -f # live logs

In addition to stdout (journald/Docker), LCM writes a persistent log file at <data-dir>/logs/lcm.log (package: /var/lib/lcm/logs/lcm.log; configurable via log_file in config.json). It rotates automatically (at 10 MB, up to 7 compressed backups, max 7 days) so you can review restarts, crashes and actions after the fact:

Terminal window
grep 'LCM-Dienst' /var/lib/lcm/logs/lcm.log # every start/stop
  • === LCM-Dienst gestartet === - on EVERY (re)start, with version, build and PID.
  • === LCM-Dienst wird beendet === - only on a clean stop (signal). A start without a following stop line means a crash / hard kill - that’s how you spot unplanned restarts.
  • Actions such as backups (system-backup erstellt), CVE/Docker scans etc. are logged too.
Section titled “Prebuilt image from Docker Hub (recommended)”

Releases are published as multi-arch images (amd64/arm64) on Docker Hub: techeve/lcm - :latest is the current stable release, :beta the pre-release, plus every version as its own tag. The CVE scanner sidecar is techeve/lcm-trivyd.

Terminal window
mkdir -p data && sudo chown 1000 data # the container writes as UID 1000
docker run -d --name lcm \
-p 9310:9310 -v "$PWD/data:/data" \
--read-only --tmpfs /tmp --cap-drop ALL \
--restart unless-stopped \
techeve/lcm:latest
docker logs -f lcm # first start: the generated admin password appears here

With Docker Compose: take the bundled docker/docker-compose.yml, remove the build: block and set image: techeve/lcm:latest - all hardening flags stay in place.

Terminal window
make docker-build # build Linux binary (incl. audits) + create image
docker compose up -d
docker compose logs -f # first start: the generated admin password appears here

On first start, the host folder ./data gets the configuration, the SQLite database and version.json. The runtime image is minimally hardened (Alpine, non-root, read-only, cap_drop: ALL). The container speaks HTTPS with a self-signed certificate by default - put a reverse proxy with a real certificate in front for public deployments.

Details and all hardening flags: Docker operation and Packaging.

Terminal window
make build # npm audit → vite build → govulncheck → go build
./bin/lcm # creates config.json, lcm.key + DB on first start

The first start prints the initial admin password once to the console.

To try things out safely with example servers and simulated data:

Terminal window
./bin/lcm --demo

Demo mode can only be enabled via this flag (it is not a config.json field) and only takes effect when seeding a fresh database. A regular installation starts empty.

The binary knows only a few flags - everything else lives in config.json:

FlagEffect
--data <dir>Data directory for config.json, app.db, lcm.key and version.json. Default: the binary’s directory; in a container typically /data.
--config <path>Path to config.json (default: inside the data directory).
--demoSeed test data (servers, packages, job histories) on the first seeding of a fresh DB.
--devDevelopment mode: allows plain HTTP (otherwise always HTTPS).
--debugRaises the log level to debug at runtime without changing config.json.
--versionPrint the version and exit.

There is also a subcommand for master-key rotation (see Security model):

Terminal window
./lcm rotate-db-key # generate a new master key, re-encrypt all fields

Handy in container/service operation to override values without touching the (possibly read-only mounted) config.json:

VariableEffect
LCM_DATAData directory (same as --data).
LCM_HOSTBind address of the web UI / REST API (overrides host).
LCM_PORTPort of the web UI / REST API (overrides port).
LCM_AGENT_HOSTBind address of the agent listener (overrides agent_host).
LCM_AGENT_PORTPort of the agent listener (overrides agent_port); 0 disables it.
LCM_BACKUP_PASSPHRASEPassphrase for automatic backups (see Backups).
LCM_RESTORE_AUTO_RESTART1/true = restart automatically after a staged restore.
TZTime zone, e.g. Europe/Berlin - tzdata is embedded in the binary, so it works even in minimal containers.
Terminal window
LCM_HOST=0.0.0.0 LCM_PORT=443 ./lcm # UI/REST on all interfaces, port 443
LCM_AGENT_PORT=0 ./lcm # disable LCM Remote (agent listener)

LCM binds up to three separate listeners - deliberately on their own ports:

Port (default)ListenerBind (default)Protocol
9310Web UI + REST API (host/port)127.0.0.1HTTPS (self-signed; --dev = HTTP)
9320Agent listener - LCM Remote, /mqtt only (agent_host/agent_port); agent_port: 0 disables it0.0.0.0HTTPS (same certificate as the UI)
9330MCP listener - optional, off by default; toggle under Settings → MCP127.0.0.1HTTP

The agent port carries only the agent interface, the UI/REST port carries none - and vice versa. Details: LCM Remote and MCP interface.

The initial admin password is in the console/journal output of the first start:

Terminal window
journalctl -u lcm | grep -A3 'Admin-Zugang' # for the .deb install

Then open https://<host>:9310 in the browser (self-signed certificate - the browser warning is expected), log in as admin and change the password.

Continue with the Quickstart.