Skip to content

Production vs Development Mode

OneSeventyFour edited this page May 15, 2026 · 2 revisions

Production vs Development mode

Backyard Hero ships two ways to run the host stack. Both produce an identical web UI on http://localhost:1776 (or, on a Pi, http://backyardhero/); the difference is where the container's contents come from.

TL;DR

Property Production (start.sh / start.bat) Development (start-dev.sh)
Image Pulls os4ivmb/backyardhero:latest from Docker Hub Builds from host/Dockerfile against the local source tree
First-run time ~2 minutes (pull) ~10–15 minutes (npm ci + next build + python deps)
Disk footprint ~600 MB image, no source needed Source tree + image + node_modules (~3 GB)
Hot reload No — code lives inside the image Yes — byh_app/, pythings/ mounted as volumes
Recommended for Operators, field deployments, Pi installs Active development, contributors, anyone tweaking firmware integration
Compose file host/run/<platform>/docker-compose.yml host/run/<platform>/docker-compose-dev.yml

File layout

Both modes' launchers and compose files live under host/run/<platform>/:

host/run/
├── pi/         # Raspberry Pi (controller; AP, NAT, systemd, install.sh)
├── osx/        # macOS development (Docker Desktop)
├── windows/    # Windows development (Docker Desktop)
└── README.md   # platform-picker

Each folder is self-contained: its compose files reference Dockerfile from the repo root (build.context: ../..), but the launcher scripts, compose files, and any platform-specific extras don't share branches with the others. No platform conditionals anywhere — if you can read pi/start.sh you understand exactly what happens on a Pi, without grep-ping across switches.

See host/run/README.md in the repo for the rationale.

Production mode

This is what you want unless you're modifying the codebase.

What it actually does

  1. Starts the TCP-to-serial bridge as a host-native Python process (talks to /dev/byh_dongle, /dev/tty.usbmodem*, or COMn).
  2. docker compose -f host/run/<platform>/docker-compose.yml pull — fetches the prebuilt image from Docker Hub. Tolerant of pull failure when offline (Pi installs that run AP-only can boot without internet using the cached image).
  3. docker compose ... up — starts the container.

The image embeds:

  • The pre-built Next.js app (next build already done at image build time).
  • The Python daemon, websocket server, and dependencies.
  • supervisord wiring everything together.

Pinning a specific version

The default tag is :latest. To pin to a specific build:

BYH_IMAGE=os4ivmb/backyardhero:v0.08 ./host/run/<platform>/start.sh

Available tags are listed at https://hub.docker.com/repository/docker/os4ivmb/backyardhero/general.

Updating

On a Pi, the right command is sudo host/run/pi/update.sh. It does git pull + docker compose pull + idempotent install.sh + systemctl restart byh-host, in one go.

On macOS / Windows, just stop the running launcher and re-run it. The script always does a docker compose pull before bringing the stack up, so you'll automatically get the newest :latest (or your pinned tag).

To roll back: pin a previous version with BYH_IMAGE=....

Development mode

For when you're editing the UI, the daemon, or hooking in changed firmware behavior.

What it actually does

The dev compose file (host/run/<platform>/docker-compose-dev.yml) is identical to production except:

  • build: . instead of image: ... — the image is built locally from host/Dockerfile.
  • ../../pythings:/app/pythings and ../../byh_app/backyardhero:/app/byh_app/ are bind-mounted into the container, so your local changes are visible immediately.
  • supervisord.dev.conf is used instead of supervisord.conf. The dev variant runs npm run dev (Next.js dev server with HMR) instead of npm run start (production build).
  • NODE_ENV=development.

On Pi specifically, the dev compose still binds port 80 (for the friendly URL) and still wires host.docker.internal:host-gateway so the in-container daemon can reach the host-side bridge. On macOS / Windows, dev compose binds only 1776 (no port 80) to avoid colliding with whatever else the dev has running.

When you change Python daemon code

The daemon (pythings/pc_daemon/pc_daemon.py) does not auto-reload. Restart the daemon process inside the container:

docker exec firework-system supervisorctl restart firework-daemon

(The Next.js app under npm run dev does auto-reload — saved JSX changes show up in the browser within a second.)

When you change firmware

Firmware (devices/os4_*/...) does not affect the Docker image at all. Build & flash separately:

devices/utils/build_receiver.sh           # receivers
devices/utils/flash_receiver.py           # routine app-only update

devices/utils/build_dongle.sh             # dongle (auto-installs arduino-cli + core)
devices/utils/flash_dongle.py             # routine app-only update

For the dongle UI flash flow, see Flashing a Dongle.

Pushing your changes back to Docker Hub (maintainers)

If you've made changes you want to publish as the next :latest, use the platform-specific build-and-push script:

host/run/osx/build_and_push_docker.sh                 # multi-arch build + push :latest and :v<host_version>
host/run/osx/build_and_push_docker.sh --tag rc1       # add an extra :rc1 tag
host/run/osx/build_and_push_docker.sh --no-push       # build locally only (single arch)

host/run/windows/build_and_push_docker.bat            # equivalent on Windows

The script reads host_version from host/config/systemcfg.json and auto-tags :v<version>. You must docker login to the Docker Hub account that owns os4ivmb/backyardhero first.

Iterating from a dev box onto a Pi

If you're SSH'd into a Pi and want to push local edits from your laptop instead of editing on the Pi directly, the recommended flow is:

# On your laptop, rsync your git-tracked-and-modified files:
cd /path/to/backyardhero_pyro
git ls-files -mo --exclude-standard \
  | rsync -avz --files-from=- ./ pi-host:/opt/backyardhero/

# On the Pi (one-time after install): make sure /opt/backyardhero is owned
# by your SSH user, otherwise rsync hits permission errors.
sudo chown -R "$USER:$USER" /opt/backyardhero

Then on the Pi, restart whatever you changed:

# Frontend (HMR picks JSX up automatically; restart just for safety):
sudo systemctl restart byh-host                     # prod
# OR (dev mode, foreground; Ctrl-C and rerun):
cd /opt/backyardhero/host/run/pi && ./start-dev.sh

# Python daemon:
docker exec firework-system supervisorctl restart firework-daemon

# Bridge (if you changed tcp_serial_bridge or flash_server):
sudo systemctl restart byh-host

See Updating the Pi for the upstream-pull equivalent.

Switching modes

Just stop the running launcher and start the other one. The two compose files use the same container name (firework-system), so docker will refuse to bring up one stack while the other is running. docker compose down (or Ctrl-C the running launcher) first.

The on-disk state — host/data/backyardhero.db, host/config/systemcfg.json, host/data/state — is shared between modes. Receiver definitions, shows, and inventory carry over.

Clone this wiki locally