
Dockerized Hardware Simulation for Angular 20+: Kiosks, Scanners, and Printers in Dev/CI to Reproduce Defects Fast
What we ran at a major airline kiosks (in Docker) so engineers could reproduce airport-only bugs on laptops and in CI—without waiting for a gate agent.
We stopped flying engineers to the hardware lab by making the hardware come to them—in Docker, on every PR.Back to all posts
If you’ve ever chased a kiosk-only bug at 5 a.m. in an airport, you know why we simulate hardware. at a major airline, we packaged our card readers, printers, and scanners into Docker, wired them to the Angular app over WebSockets/HTTP, and ran the whole thing in GitHub Actions. Defect reproduction dropped from days to minutes.
This playbook shows how to emulate peripherals with Docker for Angular 20+ apps—complete with Signals/SignalStore modeling, CI integration (GitHub Actions/Jenkins/Azure DevOps), and the record/replay flows I’ve shipped across airlines, media, and retail.
If you’re planning 2025 roadmaps and need an Angular consultant to stabilize kiosk or device-heavy apps, this is how I’d set you up—fast, measurable, and safe.
The 5 a.m. kiosk bug—and why we Dockerized devices
a major airline reality check
at a major airline, our Angular kiosk UI kept failing only at the airport. No one could reproduce the jammed-printer path or flaky scanners on dev laptops. Ops logs were noisy, and traveling to a gate wasn’t scalable.
We built Dockerized emulators for card readers, barcode scanners, and thermal printers. Engineers could spin up the entire device stack in 10 seconds, trigger failure states, and run Cypress against it locally and in CI. Repro time fell from days to minutes; flake rate dropped under 2%.
Gate-only failures
Paper-out printer states
USB flakiness when hubs power cycle
What this article covers
You’ll get a reference architecture, code snippets, CI config, and a metrics checklist to keep the system honest. If you need help, hire an Angular developer who’s done kiosk and hardware work—I’m available for remote consulting.
Architecture
Angular 20+ implementation with Signals/SignalStore
CI integration
Record/replay debugging
Metrics
Why Docker hardware simulation accelerates Angular delivery
Business impact
Hardware bugs are expensive: travel, equipment access, and brittle test rigs. Docker makes peripherals software-addressable, so your Angular app and e2e tests talk to predictable endpoints. That predictability is what lets us ship on time without gambling on lab availability.
Defect reproduction from days → minutes
Fewer field trips to physical devices
Deterministic CI—no flaky USB
Engineering impact
By normalizing device protocols into HTTP/WebSocket APIs with typed event schemas, we can model device state with Signals and SignalStore, test offline flows, and run the full matrix on GitHub Actions/Jenkins/Azure DevOps.
Typed contracts over WebSockets/HTTP
Offline-tolerant UX paths tested regularly
CI-friendly: ephemeral, parallelizable
Reference architecture: Dockerized device emulators for Angular 20+
docker-compose.yml
version: '3.9'
services:
device-hub:
build: ./emulators/device-hub
environment:
- WS_PORT=8081
- HTTP_PORT=8080
ports:
- "8080:8080"
- "8081:8081"
healthcheck:
test: ["CMD", "curl", "-f", "http://device-hub:8080/health"]
interval: 5s
timeout: 2s
retries: 20
printer-sim:
build: ./emulators/printer-sim
environment:
- PRINTER_MODEL=escpos
- FAILURE_MODE=none # none|paper_out|jam|overheat
volumes:
- ./fixtures/receipts:/app/receipts
scanner-sim:
build: ./emulators/scanner-sim
environment:
- BARCODE_STREAM=./fixtures/barcodes.json
volumes:
- ./fixtures:/app/fixtures
cardreader-sim:
build: ./emulators/cardreader-sim
environment:
- CARD_PROFILE=visa
record-replay:
build: ./tools/record-replay
volumes:
- ./scenarios:/app/scenarios
ports:
- "9090:9090"
networks:
default:
name: devicesCore components
- Device Hub: wraps protocols (ESC/POS, OPOS, USB HID, serial) into clean REST + WebSocket APIs; publishes typed events.
- Emulators: small containers that mimic peripherals: success + failure states (paper-out, jam, offline, busy).
- Record/Replay: captures event streams for defect scenarios; replays deterministically in CI.
- Angular app: consumes APIs via a SignalStore; uses PrimeNG/Angular Material for operator UI.
Device Hub (Node.js or .NET)
Emulators (printer, scanner, card reader)
Record/Replay service
Angular app (Nx workspace)
docker-compose for dev
Spin everything locally with one command.
Model device state with Angular Signals + SignalStore
// device-events.ts
export type DeviceEvent =
| { type: 'printer.status'; online: boolean; paperPct: number }
| { type: 'printer.error'; code: 'paper_out' | 'jam' | 'overheat'; message: string }
| { type: 'scanner.scan'; text: string; symbology: 'qr' | 'code128' | 'pdf417' }
| { type: 'cardreader.result'; status: 'approved' | 'declined'; authCode?: string }
| { type: 'hub.heartbeat'; ts: number };
export interface DeviceState {
printerOnline: boolean;
paperPct: number;
lastPrinterError?: string;
scannerConnected: boolean;
lastScan?: { text: string; symbology: string };
payment?: { status: 'approved' | 'declined' };
kioskMode: 'online' | 'offline' | 'degraded';
}
export const initialDeviceState: DeviceState = {
printerOnline: false,
paperPct: 100,
scannerConnected: true,
kioskMode: 'offline', // deterministic initial value
};// device.store.ts (NgRx SignalStore)
import { signalStore, withMethods, withState } from '@ngrx/signals';
import { effect } from '@angular/core';
import { webSocket, WebSocketSubject } from 'rxjs/webSocket';
import { retry, tap } from 'rxjs/operators';
import { DeviceEvent, initialDeviceState } from './device-events';
export const DeviceStore = signalStore(
withState(initialDeviceState),
withMethods((store) => {
let socket: WebSocketSubject<DeviceEvent> | null = null;
function connect(url = 'ws://localhost:8081/ws') {
socket = webSocket<DeviceEvent>({ url });
socket
.pipe(
retry({
delay: (err, count) => Math.min(1000 * 2 ** count, 15000), // exponential backoff
}),
tap((msg) => dispatch(msg))
)
.subscribe();
}
function dispatch(msg: DeviceEvent) {
switch (msg.type) {
case 'printer.status':
store.patchState({ printerOnline: msg.online, paperPct: msg.paperPct, kioskMode: 'online' });
break;
case 'printer.error':
store.patchState({ lastPrinterError: msg.code, kioskMode: 'degraded' });
break;
case 'scanner.scan':
store.patchState({ lastScan: { text: msg.text, symbology: msg.symbology } });
break;
case 'cardreader.result':
store.patchState({ payment: { status: msg.status } });
break;
case 'hub.heartbeat':
if (!store.kioskMode() || store.kioskMode() === 'offline') {
store.patchState({ kioskMode: 'online' });
}
break;
}
}
effect(() => {
// reconnect on mode change if we drop offline
if (store.kioskMode() === 'offline') connect();
});
return { connect };
})
);<!-- device-banner.component.html -->
<p-toast *ngIf="deviceStore.lastPrinterError()" severity="warn" summary="Printer" [detail]="deviceStore.lastPrinterError()"></p-toast>
<p-tag [severity]="deviceStore.kioskMode() === 'online' ? 'success' : 'warning'">
{{ deviceStore.kioskMode() | titlecase }}
</p-tag>Typed event schema
Create a typed event schema and a SignalStore that translates WebSocket events into deterministic UI state. This keeps SSR and tests stable while preserving real-time updates.
Stable initial values for SSR/tests
Strict null checks
Exponential reconnect
DeviceStore example
CI integration: GitHub Actions, Jenkins, Azure DevOps
# .github/workflows/e2e.yml
name: e2e-devices
on: [push, pull_request]
jobs:
e2e:
runs-on: ubuntu-latest
services:
device-hub:
image: ghcr.io/yourorg/device-hub:latest
ports: ["8080:8080", "8081:8081"]
options: >-
--health-cmd="curl -f http://localhost:8080/health || exit 1"
--health-interval=5s --health-timeout=2s --health-retries=20
printer:
image: ghcr.io/yourorg/printer-sim:latest
scanner:
image: ghcr.io/yourorg/scanner-sim:latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm ci
- run: npx nx run webapp:e2e --configuration=ci
- name: Upload emulator logs
uses: actions/upload-artifact@v4
with:
name: emulator-logs
path: emulators/**/logs/*.logSpin up emulators in CI
Use services or docker-compose in CI. Health checks gate tests until emulators are ready. Parallelize scenarios (happy path, paper-out, offline) to keep pipelines under 10 minutes.
Health checks
Wait-for scripts
Parallel matrices
GitHub Actions example
Record and replay failure scenarios
# record a scenario for printer paper-out
curl -X POST http://localhost:9090/record/start -d '{"name":"paper_out_checkout"}'
# trigger actions in the app, then stop
curl -X POST http://localhost:9090/record/stop
# replay in CI
curl -X POST http://localhost:9090/replay -d '{"name":"paper_out_checkout"}'// cypress/e2e/paper-out.cy.ts
describe('Paper out flow', () => {
it('shows recovery guidance and prevents duplicate charges', () => {
cy.request('POST', 'http://localhost:9090/replay', { name: 'paper_out_checkout' });
cy.visit('/checkout');
cy.contains('Printer out of paper').should('be.visible');
cy.contains('Retry Print').should('be.disabled'); // until paper restored
});
});Why record/replay
When a defect appears in staging, record the event stream (with timestamps) and add the scenario to your fixtures. Every engineer can reproduce with one command; CI runs it on every PR.
Deterministic failures
Shareable artifacts
One-command repro
CLI example
Security and compliance in simulated environments
Boundary hygiene
Keep emulators clearly segmented from real devices and data. Use synthetic tokens and barcodes. Restrict ports to the CI network; scrub logs before upload.
No real PAN data
Mask tokens in logs
Least-privileged networks
Secrets and images
Store images in private registries, produce SBOMs, and verify signatures. In regulated environments (PCI, SOC 2), keep emulator fixtures separate from production traffic and rotate CI credentials frequently.
Private registries
SBOMs
Signature verification
When to Hire an Angular Developer for Legacy Rescue
Signals that you need help
If your AngularJS-to-Angular migration stalled, or your kiosk app has field-only defects, a senior Angular engineer can stand up Dockerized emulators, wire a SignalStore, and stabilize CI in 2–4 weeks. This is the playbook I used at a major airline, a global entertainment company, and retail fleets.
“Works on my lab rig” bugs
Field-only failures
Flaky e2e tied to USB
What I do in week one
I map device protocols, define typed events, implement a DeviceStore with Signals, and ship a green CI with 3–5 critical scenarios. If you need a remote Angular developer or Angular consultant, I’m available for short, focused engagements.
Assess protocols
Design event schema
Automate CI services
Metrics that keep you honest
Delivery metrics
Track defect reproduction time (target: minutes), e2e duration (target: <10 min), and flake rate (target: <2%). Use Angular DevTools + Lighthouse for UI budgets; feed telemetry into GA4/Firebase Logs or OpenTelemetry.
Defect repro time
E2E duration
Flake rate
Runtime metrics
Instrument emulator uptime (>99.5% in CI), categorize errors (paper_out, jam, offline), and visualize backoff/reconnect histograms in dashboards (D3/Highcharts).
Emulator uptime
Error taxonomy
Backoff behavior
Key takeaways
- Simulate kiosk peripherals with Docker to cut defect reproduction from days to minutes.
- Use typed event schemas over WebSockets and a SignalStore to model device state deterministically.
- Run emulators in CI (GitHub Actions/Jenkins/Azure DevOps) for stable, reproducible e2e tests.
- Record/replay device scenarios to debug offline, paper-out, jammed, or driver-error states.
- Instrument metrics: repro time, e2e duration, flake rate, emulator uptime, and error taxonomies.
Implementation checklist
- Define device interfaces and typed event schemas for each peripheral.
- Create a Device Hub container that normalizes device protocols to HTTP/WebSocket.
- Model device state in Angular with Signals + SignalStore and deterministic initial values.
- Add a docker-compose stack for local dev; mount fixtures and logs as volumes.
- Use Cypress/Playwright e2e tests pointing at emulators; add record/replay fixtures.
- Integrate into CI with health checks, wait-for scripts, and artifact uploads.
- Gate merges with stability metrics: flake rate <2%, e2e <10 min, emulator uptime >99.5%.
- Document recovery: exponential reconnect, backpressure, and offline-first UX paths.
Questions we hear from teams
- What does an Angular consultant do for kiosk or device-heavy apps?
- Model device state with Signals/SignalStore, stand up Dockerized emulators, wire WebSocket/HTTP adapters, and stabilize CI with deterministic e2e. I also add telemetry, metrics, and rollback plans so you can ship confidently.
- How long to implement Docker hardware simulation and CI?
- A focused engagement is 2–4 weeks: week 1 for architecture, event schemas, and a working compose stack; weeks 2–3 for Angular integration and 3–5 critical scenarios; week 4 for CI hardening and metrics.
- How much does it cost to hire an Angular developer for this?
- Scope-driven. Most teams complete a baseline simulation + CI in 2–4 weeks. I work as a remote Angular contractor or consultant; we align on fixed-scope or weekly rates after a short assessment.
- Will this work with Jenkins or Azure DevOps instead of GitHub Actions?
- Yes. The pattern is portable: docker-compose or services, health checks, wait-for scripts, and artifact uploads. I’ve shipped it on GitHub Actions, Jenkins, and Azure DevOps across AWS/Azure/GCP.
- Do we need real devices at all?
- Yes—for final acceptance and driver compatibility. Emulators cover 80–90% of flows and all failure paths quickly. Keep a small lab for smoke tests and firmware updates; run everything else in Docker.
Ready to level up your Angular experience?
Let AngularUX review your Signals roadmap, design system, or SSR deployment plan.
NG Wave
Angular Component Library
A comprehensive collection of 110+ animated, interactive, and customizable Angular components. Converted from React Bits with full feature parity, built with Angular Signals, GSAP animations, and Three.js for stunning visual effects.
Explore Components