This project aims to provide local control of Petkit devices. It communicates directly with your devices on your local network and creates entities in Home Assistant over MQTT for seamless integration.
This is the pyrox fork of
dwyschka/localkit. It carries its own
image (see "Image layout") and code changes on top of upstream main.
The main one, offered upstream as a pull request:
CLOUD_STORAGE recordings are encoded to MP4 once per event instead of on
every ~4 s segment (see fix/cloud-storage-encode). Re-encoding the whole
event on every segment made the upload request grow quadratically with the
event length, and device retries ran two encodes at once; the container was
OOM-killed on longer events.
Smaller fixes: dev_multi_config answers without a device implementation
instead of HTTP 500 (W7H), the device edit form no longer resets times to
00:01 (cherry-picked from upstream beta), and the Services page copes
without s6.
The fork releases with the shared Forgejo workflows from pyrox/actions,
copied unchanged from its templates. Forgejo runs only .forgejo/workflows,
so the upstream GitHub workflow stays inactive here.
| Workflow | Trigger | Result |
|---|---|---|
semver-bump.yaml | push to main | raises .bumpversion.toml and tags vX.Y.Z, only if files that end up in the image changed |
docker.yaml | tag v*, Mondays 05:15, manual | builds the latest tag as X.Y.Z, X.Y, X and latest |
Repository variables: IMAGE_NAME=localkit, and SEMVER_RELEASE_PATHS
with the same exclusion list as the build's relevant-paths (the image
copies the whole repository, so everything except CI files counts).
Deployments pin the version tag without a digest. The weekly rebuild pushes the same version tag again, and the Forgejo registry deletes the previous manifest of an overwritten tag: a pinned digest of an older build returns 404. The version tag itself always resolves.
The fork's versions continue from upstream 1.2.0, the last upstream tag
in its history, and are independent of later upstream versions.
New settings:
| Variable | Default | Meaning |
|---|---|---|
LOCALKIT_CLOUD_STORAGE_ENCODE_DELAY | 60 | Seconds without a new segment before an event is encoded |
LOCALKIT_FFMPEG_THREADS | 2 | libx264 threads for that encode; memory grows with it |
LOCALKIT_FFMPEG_TIMEOUT | 900 | Time limit of one encode in seconds |
The encode runs from the Laravel scheduler (localkit:encode-cloud-storage
every minute), so a scheduler must run. A queue worker is not required:
with QUEUE_CONNECTION=sync the job runs inside the scheduler.
The fork does not build on upstream's base image
ghcr.io/dwyschka/localkit-docker (serversideup/php with nginx and s6),
which was last rebuilt in August 2026 and stayed on PHP 8.4.24. The
Dockerfile here starts from the official php:<version>-fpm-trixie image
with an exact PHP version that Renovate updates; renovate.json keeps it on
PHP 8.4. Extensions come from install-php-extensions (gd, intl, opcache,
pcntl, pdo_mysql, pdo_pgsql, zip; no redis, which Localkit does not use),
Composer installs without dev
packages, and Debian's ffmpeg handles recordings and thumbnails. The
go2rtc binary of the old base image is gone: every camera runs its own
go2rtc, and Localkit only talks to that over HTTP (config/go2rtc.php).
The image runs php-fpm only, as www-data, with tini as PID 1. Each
Localkit process is a container of its own in the same pod, started from
the same image:
| Container | Command | Replaces |
|---|---|---|
| init container | localkit-init /export | serversideup's AUTORUN (migrations) |
| php-fpm | default command, 127.0.0.1:9000 | php-fpm under s6 |
| nginx | nginxinc/nginx-unprivileged with docker/nginx/localkit.conf | nginx under s6 |
| listener | php artisan localkit:listen | s6 service localkit-listen |
| Home Assistant bridge | php artisan localkit:homeassistant | s6 service localkit-homeassistant |
| scheduler | php artisan schedule:work | s6 service localkit-scheduler |
localkit-init (docker/localkit-init) copies public/ and the nginx
configuration into a volume shared with the nginx container, so nginx
always serves the files of the running image, and then runs
php artisan migrate --force. nginx listens on 8080 (HTTP) and 8443
(HTTPS) and expects the device certificate as /etc/nginx/tls/tls.crt and
tls.key. docker/php/ holds the PHP and php-fpm settings, with the same
limits as the old base image (256M memory, 100M uploads).
Without s6 the System, Services page of the web UI is hidden. A process
that exits restarts its container; sh -c 'kill 1' inside a container restarts it
on purpose, for example the Home Assistant bridge after adding a device, as
it publishes the discovery messages only on start.
The deployment in pyrox/k3s-prod (apps/localkit) shows the complete
pod.
Back to upstream: once upstream contains the change and its image ships a
current PHP, point deployments back to ghcr.io/dwyschka/localkit (one
container with nginx and s6) and archive this repository. Until then, sync
with git fetch upstream && git merge upstream/main on a branch and open a
pull request here.
Upstream changes to Dockerfile, s6/ or entrypoint.d/ do not apply to
this image and show up as conflicts; keep this layout and carry over only
what the app needs (a new service becomes a container in the deployment).
Do not push upstream's tags (1.2.0 and older, without v) here:
scheduled builds check out the latest tag, and would then publish upstream
code without the fork's changes.
As an internal MQTT broker, localkit-broker is required for the system to function correctly.
Litter boxes
t4)t7)Feeders
d3)d4)d4h)d4sh)Water fountains
w7h)Bluetooth accessories
k3)w5)Localkit integrates with Home Assistant over MQTT, automatically exposing your Petkit devices as entities via MQTT discovery. See the Home Assistant integration guide for setup details.
For full setup instructions, the complete list of exposed entities, and detailed documentation, visit localkit.io.
Content type
Image
Digest
sha256:825e5bf67…
Size
369.3 MB
Last updated
3 days ago
docker pull ricariel/localkit