PhilippTheServer

Blog

RSS $ subscribe philipptheserver.com/feed.xml
  1. I Reverse-Engineered My Smart Ring BLE Protocol Clean-Room

    COLMi R02, Realtek RTL8762E, BLE from Linux and a Pixel 10a. 16-byte framing, an unenforced checksum, and a clean-room method with evidence levels.

  2. Tracking Meals and Automating Shopping With an MCP Server

    An MCP server for meal tracking and grocery shopping automation: planned meals minus live stock, and why the v2 rebuild derives stock from an event journal.

  3. Building a Headless Storefront Against the OpenTaberna API

    A headless storefront for OpenTaberna: a 37-line API client, TypeScript types generated from OpenAPI, and a compiler that catches field typos.

  4. Automating Web Shop Order Processing Before the Catalogue

    Web shop order processing automated first: Stripe webhook payment confirmation, stock reservations, outbox-driven DHL labels, two manual steps.

  5. OpenTaberna: Headless, Open-Source E-Commerce You Self-Host

    OpenTaberna is headless, open-source e-commerce: an Apache-2.0 shop API on FastAPI and PostgreSQL that you self-host with Docker Compose.

  6. Traefik's 60-Second readTimeout Covers the Whole Request Body

    "Expected filesize of 104857600 bytes but read 29491200": Traefik's readTimeout defaults to 60s and bounds the whole request body, so slow uploads get cut.

  7. Multicast Service Discovery on a Host With a WireGuard Interface

    "Required key not available" (ENOKEY) on multicast sendto: kernel WireGuard has no peer for 239.0.0.0/8. Filter interfaces on IFF_MULTICAST and IFF_POINTOPOINT.

  8. Running a Self-Hosted Tailscale Control Server Behind a Reverse Proxy

    Headscale behind Traefik: tls.options=no-h2@file with alpnProtocols http/1.1, because the Tailscale noise handshake breaks when ALPN negotiates h2.

  9. nginx: The Map Variable That Fixes the URI in proxy_pass

    nginx: [emerg] invalid URI prefix in proxy_pass: a regex location needs a bare upstream, and a map on $uri keeps a path-specific Cache-Control header.

  10. A Daily Digest of What Actually Got Merged, Built from MQTT and a Local LLM

    paho-mqtt subscriber to SQLite plus a local LLM: a daily digest that summarises merged pull requests per repository from diff stats, no cloud API.

  11. The NTP Clock Jump That Made the Scheduler Skip a Week of Measurements

    time.time() vs time.monotonic() in a Python scheduler: a backwards NTP step delayed runs by nine days. Monotonic durations and a jump-injecting test.

  12. fsGroupChangePolicy: OnRootMismatch and the setgid Invariant That Makes It Sound

    Pod stuck in ContainerCreating from fsGroup recursive chown: fsGroupChangePolicy: OnRootMismatch, and why the setgid bit on the volume root makes it safe.

  13. kube-proxy in iptables Mode and Native nftables Rules in the Forward Chain

    kube-proxy iptables-nft sync silently aborts on native nftables rules a mesh agent adds to the FORWARD chain. Fix: nft delete rule by handle, ip and ip6.

  14. The Whole Estate in One Article: How Every Layer Fits Together

    Self-hosted platform build order: why mesh, cluster, Ceph storage, identity and observability must precede model serving, shown with Compose depends_on.

  15. Documentation as a Repository: Publishing a Wiki.js Site from Reviewed Markdown

    Wiki.js Git storage for docs as code: publish reviewed Markdown and fail a pull request when src/ changes without docs/, checked with git diff.

  16. Testing Infrastructure Code by Executing Its Real Expressions

    Ansible Templar in pytest: render the shipped combine(recursive=True) expression from defaults/main.yml instead of a shallow Python paraphrase.

  17. Keeping Documentation Honest with an OpenAPI Snapshot Diff

    FastAPI app.openapi() snapshot test in pytest: fail CI when the live OpenAPI schema drifts from the committed openapi.snapshot.json.

  18. When Microservice Decomposition Is the Wrong Default

    import-linter forbidden contract as a module boundary: a FastAPI modular monolith instead of early microservices, and when a network split pays off.

  19. One Local Endpoint for Every Agent Session

    OPENAI_BASE_URL=http://localhost:8080/v1 for every AI tool: one local FastAPI proxy that logs each request and refuses internal-only projects.

  20. Treating Issue Bodies as Untrusted Input

    Prompt injection via GitHub issue bodies: fence untrusted text and accept model output only as a fixed JSON action schema, tested without a model.

  21. From a Large Allowlist to Three Denials: Permissions for a Coding Agent

    bwrap --unshare-net --cap-drop ALL instead of a command allowlist: sandboxing a coding agent's shell so rm -rf and curl cannot leave the workspace.

  22. Self-Hosted LLM Inference: Serving, Benchmarking and Agent Guardrails

    Ollama OpenAI-compatible /v1/chat/completions: a repeatable benchmark on your own tasks and a dry-run plus --approve guardrail for infrastructure agents.

  23. Verifying an Agent's Work Against Reality, Not Its Own Report

    git diff --quiet HEAD and a re-run test command in a separate CI job: verifying a coding agent's work from repository state instead of its own report.

  24. Testing an Agent Harness Without Ever Calling the Model

    Unit testing a coding agent's tool-call permission layer with pytest and recorded fixtures: allow, deny or ask decisions with no model or API key.

  25. The Label as Contract: Consent and Priority as the Whole Queue

    agent-ready label as the only consent gate for a coding agent: FIFO order by label time and a re-read-then-swap claim so two runs never share an issue.

  26. A Deterministic Daemon That Turns a Labelled Issue Into a Pull Request

    git apply --check before git commit: an issue-to-PR daemon where the model only returns a patch and plain Python commits, pushes and opens the PR.

  27. Testing That a Canvas Diagram Actually Painted Pixels

    getImageData in Playwright to assert a canvas actually drew pixels, since jsdom has no canvas renderer and DOM assertions pass on a blank canvas.

  28. Turning Alertmanager Webhooks Into Retained MQTT State

    Alertmanager webhook to MQTT retained messages: clear stale state on resolve with an empty retained payload, keeping current alerts in Redis by fingerprint.

  29. A Dead-Man's Switch for the Monitoring System Itself

    Alertmanager dead man's switch with expr: vector(1) == 1: an always-firing Heartbeat alert and an external receiver that alerts when pings stop.

  30. Probing the Names You Publish, Not the Services You Run

    blackbox_exporter __param_target relabel_configs: probe the published hostname to catch DNS, TLS expiry and proxy errors that /healthz cannot see.

  31. Prometheus HTTP Service Discovery Backed by a Live Inventory

    Prometheus http_sd_configs: the targets and labels JSON an inventory endpoint must return, refresh_interval, and why targets persist when it is down.

  32. Host Probes via the Textfile Collector Pattern

    node_exporter --collector.textfile.directory: torn .prom writes fixed by write-temp-then-rename, and stale files caught with a last-run timestamp alert.

  33. Self-Hosted Identity with Keycloak: Central Offboarding and Forward Auth

    Keycloak "invalid_grant" "Account disabled": one admin API call disables a user for every OIDC client, plus oauth2-proxy forward auth for apps without login.

  34. A Rules File for Health Evaluation, Validated at Load Time

    Go rules file with gopkg.in/yaml.v3: yaml.Unmarshal accepts operator: greter. Validate operators and metric names at load time so startup fails.

  35. Merging Concurrent Polls Without Ever Serving a Half-Built Graph

    atomic.Pointer swap of a fully merged snapshot so concurrent polls never serve a torn read, a bug go test -race cannot see. errgroup with timeouts.

  36. An Entity Graph as the Data Model for a Heterogeneous Estate

    Go entity graph data model: one Entity struct with Kind and Attrs, typed contains, dependsOn and runsOn edges, health rollup and cycle detection.

  37. Forward Auth: Putting Real Authentication in Front of Software That Has None

    Traefik forwardAuth with X-Auth-Request-User: strip the header first or clients can spoof it. oauth2-proxy and Keycloak in front of apps with no login.

  38. Enforcing Client Identity Alongside Realm Roles

    Keycloak azp claim: a realm role does not show which client the token was issued to. Enforcing azp next to role checks in a FastAPI dependency.

  39. OIDC Client Shapes: A Token-Validating API Is Not a Login Client

    Keycloak directAccessGrantsEnabled on a token-validating API lets it mint tokens. bearerOnly client, oidc-audience-mapper and a PyJWT aud check.

  40. Realm-as-Code: Reconciling Clients and Roles But Never Users or Signing Keys

    kc.sh import with --override=true recreates the realm, dropping users and signing keys. Reconciling only clients and roles via the Keycloak Admin REST API.

  41. DNS Wildcards and Empty Non-Terminals: The Answer That Is Not an Error

    A stale _acme-challenge TXT record makes a DNS wildcard return NODATA or NXDOMAIN: RFC 4592 closest encloser, empty non-terminals and a zone check.

  42. WireGuard Route Selection: Per-Host /32 Versus Subnet Routes With NAT

    WireGuard AllowedIPs overlap: a /24 on a gateway peer and a /32 on a direct peer break the return path. Longest-prefix-match and a /32 on both ends.

  43. Group-Based Default-Deny Instead of Hand-Maintained Peer Lists

    NetBird Default policy replaced by group-based default-deny: auto_groups on setup keys, role:/tier: group names, and a script diffing live policies.

  44. Separating 'Cannot Ever' From 'Currently Broken' in Sweep Automation

    Sweep automation: a 404 cannot tell "never supported" from "currently broken". An explicit cannot-ever allowlist with required reasons, and its tests.

  45. External Secrets with Offline JWT Validation When Vault Cannot Reach the Cluster

    Vault kubernetes auth "failed to validate token" tokenreviews i/o timeout: jwt auth with jwt_validation_pubkeys from sa.pub for External Secrets.

  46. Overlay Mesh Networking with NetBird: Peer Addressing and the Public DNS Fallthrough

    NetBird mesh hostnames resolve to the public host via a zone wildcard when the mesh resolver is down. Fix: a *.mesh blackhole record and accept-new.

  47. cert-manager for Mesh-Only Names That No ACME Challenge Can Reach

    Waiting for HTTP-01 challenge propagation, no such host: ACME cannot issue for private names. A cert-manager CA ClusterIssuer and trust distribution.

  48. How a VPN Client Silently Shrinks Your Pod Network MTU

    Frag needed and DF set (mtu = 1420): a VPN hop below the CNI's auto-detected 1500 MTU stalls large pod transfers. Reproducing it and pinning the MTU.

  49. ndots and the Accidental Search-Domain Leak in Pod DNS

    dnsConfig.searches with ndots:5 lets an internal wildcard answer external names. Why ndots:1 only half fixes it and a trailing dot fully does.

  50. A DNS Canary CronJob as a Regression Test for Split-Horizon Resolution

    CoreDNS Corefile rewrite regressions caught by a busybox nslookup CronJob that checks internal, external and NXDOMAIN names and fails the Job.

  51. fsGroup Recursive chown Hangs on Large Volumes

    fsGroupChangePolicy: OnRootMismatch stops kubelet re-chowning every file on each mount. Why fsGroup pods restart slowly, and when fsGroupPolicy None applies.

  52. Object Storage as the Durability Boundary for a Container Registry

    MANIFEST_UNKNOWN after losing a registry PVC: REGISTRY_STORAGE s3 on MinIO keeps blobs through helm uninstall. What it does not fix: garbage-collect.

  53. Importing Pre-Existing Storage as a Static PersistentVolume

    persistentVolumeReclaimPolicy: Retain with volumeName and claimRef binds a PVC to existing data. What Released means and how to rebind safely.

  54. Ceph CSI StorageClasses: RBD for Block, CephFS for Shared Volumes

    Multi-Attach error for volume: Ceph RBD is ReadWriteOnce by design. When to use rook-ceph-block and when rook-cephfs for ReadWriteMany.

  55. Breaking the Circular Dependency in GitOps Secret Delivery

    External Secrets SecretStore tokenSecretRef needs a Vault token before GitOps can deliver one. Breaking the loop with one out-of-band secret.

  56. Pinning Image Tags to Git SHAs Because GitOps Diffs Manifests, Not Registries

    image :latest never triggers an Argo CD rollout because GitOps diffs manifest text. Tagging with git rev-parse --short HEAD and committing it back.

  57. Sync Waves and the CRD-Before-Consumer Ordering Problem

    no matches for kind "Widget" in version: CRD and CR in one Argo CD sync. sync-wave "-1" fixes ordering; Prune=false stops a CRD prune wiping CRs.

  58. ApplicationSets: Onboarding a Workload With a Directory and a Pull Request

    ApplicationSet git directory generator on services/*: one Argo CD Application per directory, requeueAfterSeconds, and where sync errors show up.

  59. Alerting on Symptoms Instead of Metrics: Designing Alerts People Do Not Ignore

    probe_success == 0 via blackbox_exporter instead of CPU alerts, plus a dnspython check that treats NOERROR with an empty answer as down.

  60. Argo CD App-of-Apps: Bootstrapping a Bare Cluster from One Root Application

    Argo CD app-of-apps: a root Application with directory recurse over apps/ bootstraps a bare cluster from one kubectl apply, with pruning.

  61. The Transactional Outbox: Never Losing a Job You Already Committed

    Transactional outbox in Postgres: commit-then-enqueue loses jobs on a crash. A SKIP LOCKED relay to Redis Streams and an idempotent consumer.

  62. Running Alembic Migrations Once, Before the Workers Fork

    alembic upgrade head in app code runs once per gunicorn worker and races. Running it in the entrypoint under pg_advisory_lock, before exec.

  63. A Multi-Service docker-compose That a New Contributor Can Actually Start

    depends_on condition: service_healthy: why a plain depends_on list races, with Postgres, Redis and Keycloak healthchecks, --import-realm and a smoke test.

  64. Runtime Configuration for an Angular Container Without Rebuilding

    Angular environment.ts is baked in at ng build. Load config.json via APP_INITIALIZER, generated by envsubst in /docker-entrypoint.d at container start.

  65. Layering an Angular Application: core, shared, features and the Rule That Holds

    eslint-plugin-boundaries element-types: enforcing one dependency direction between Angular core, shared and features folders, with a failing lint example.

  66. Designing a Docker Image Build API: Job Model and Streamed Logs

    Docker SDK ImageBuild returns a nil error when RUN fails: read the error field in the JSON build stream. A Go job model with POST /builds and live logs.

  67. Embedding a Built Frontend Into a Go Binary with go:embed

    //go:embed dist with http.FileServer returns 404 on SPA client routes after a refresh: an index.html fallback, serving the API from the same Go binary.

  68. Rootless BuildKit: What fuse-overlayfs Costs on a Cold Cache

    Rootless BuildKit with --oci-worker-snapshotter=fuse-overlayfs needs no --privileged, but cold-cache builds pay a FUSE round-trip per file. Measuring it.

  69. Running BuildKit as a Remote Builder Without a Docker Daemon

    open /var/run/docker.sock: no such file or directory from docker buildx on a containerd CI worker: run buildkitd with mTLS and use the remote driver.

  70. Trusting an Internal and a Public Certificate Authority in One Process

    curl: (60) SSL certificate problem after SSL_CERT_FILE points at an internal CA: it replaces the trust store. Append with update-ca-certificates instead.

  71. A 500 That Was a Misconfigured Client, Not an Auth Failure

    resp.json()["active"] raises KeyError when token introspection returns 401 invalid_client. Check the status code first and report a client config error.

  72. A Confidential OIDC Client That Exists Only to Introspect Tokens

    invalid_client "Parameter client_secret is missing" on Keycloak token introspection for a public SPA client: add a confidential introspection-only client.

  73. Building the Dry-Run Path First, and Testing That It Sends Nothing

    if dry_run: return guards drift as side effects are added. Split the tool into a pure plan() and apply(), and test that dry run never calls the client.

  74. Running Ceph on Commodity Hardware: Failure Domains and the Four Things That Bite

    ceph osd df --format json: alert on the fullest OSD, not the mean, because one full OSD stops writes. Plus CRUSH failure domains and monitor quorum.

  75. Scoring Tool Calls by Parsed Structure Instead of String Equality

    Scoring LLM tool calls: parse the arguments JSON first so key order and 5 vs 5.0 pass, wrong tools fail, and extra fields follow per-case rules.

  76. Why a Leaderboard Score Does Not Predict Your Workload

    LLM leaderboard scores do not predict a narrow workload. A 20 to 50 case task eval with a deterministic JSON schema scorer instead of an LLM judge.

  77. Making a Benchmark Deterministic Against a Server Tuned for Interactive Use

    LLM output still varies at temperature 0 with a fixed seed because batching changes float order. Repeat each prompt and compare mean and stdev.

  78. An OpenAI-Compatible Proxy in Front of a Local Model Server

    /v1/chat/completions behind a FastAPI proxy: bearer API-key auth, unbuffered SSE streaming, and a /healthz that cannot trigger a model load.

  79. Serving Several Local Models on One GPU with On-Demand Loading

    llama-swap config.yaml with cmd, ttl and healthCheckTimeout: loading llama-server models on demand and unloading idle ones on a single GPU.

  80. Sizing One GPU for Local LLM Inference: VRAM, Quantisation and KV Cache

    llama-bench -ctk q8_0 -ctv q8_0 vs f16: estimating VRAM for GGUF weights plus a KV cache that grows with context, and catching slow KV cache types.

  81. One Dashboard on Five Unrelated Backends: Normalising at the Boundary

    asyncio.gather with return_exceptions=True in a FastAPI BFF: map two upstream APIs onto one schema and return partial data with health when one is down.

  82. Preventing Overselling with a Database CHECK Constraint

    violates check constraint "stock_not_negative": why check-then-decrement oversells under READ COMMITTED, and a single conditional UPDATE that cannot.

  83. Idempotent Payment Webhooks with a Deduplication Table

    Payment webhook processed twice on retry: dedupe on the provider event ID with a primary key, committed in the same transaction as the side effect.

  84. Modelling an Order Lifecycle as an Explicit State Machine

    Python order status as a state machine: a transition table and one transition_to method that raises InvalidTransition on moves like shipping a cancelled order.

  85. MQTT Persistent Sessions and Retained Messages for Offline Subscribers

    MQTT clean_session=False only persists with a stable client ID, QoS 1 and Mosquitto persistence true. Retained messages give new subscribers the last value.

  86. A Shared Multi-Stage Base Image for a C++ Service Fleet

    vcpkg install pistache rebuilt in every service Dockerfile: a shared, versioned multi-stage base image that compiles C++ dependencies once for the fleet.

  87. Migrating Redis Consumers from Python to C++ on Constrained Hardware

    redis-plus-plus xreadgroup times out before its block timeout: set socket_timeout to 0. Replacing idle Python Redis stream consumers with C++.

  88. UUID Case Normalization as a Recurring Cross-Service Bug

    Uppercase vs lowercase UUID keys cause silent cache and database lookup misses between services. Normalise once at the boundary with str(uuid.UUID(value)).

  89. Docker Compose or Kubernetes: What the Control Loop Actually Buys You

    Docker Compose restart: always vs a Kubernetes Deployment: what happens when the host dies, and what the control loop costs in debugging.

  90. Sizing Redis maxmemory Against a Stream Trim Threshold

    OOM command not allowed when used memory exceeds maxmemory on XADD MAXLEN: why the stream never trims itself, and why a standalone XTRIM still works.

  91. A Single-Writer Rule for Configuration Held in Redis

    SET NX PX lease plus a Lua script that checks the fencing token, bumps a version and publishes, so exactly one process writes a Redis config key.

  92. Chaining Single-Purpose Redis Consumers into a Processing Pipeline

    XREADGROUP, XACK and XAUTOCLAIM: splitting one Python consumer into single-purpose Redis Streams stages with at-least-once delivery and crash recovery.

  93. Tracking Moving Objects with Redis Sorted Sets and an Atomic Lua Tick

    ZINCRBY updates one member only: a Lua script via redis-cli --eval advances every sorted set score atomically and returns members past a threshold.

  94. Auto-Unsealing Vault Without Cloud KMS or a TPM, Using Tang and Clevis

    clevis encrypt sss with Tang pins to auto-unseal Vault without cloud KMS or a TPM: binding Shamir key shares, a systemd unit, and the t threshold.

  95. Runtime Secret Injection with a Vault Agent Sidecar and a Wrapped AppRole

    secret_id_response_wrapping_path in Vault Agent approle auto_auth: a sidecar that bootstraps from a single-use wrapped secret_id and renders secrets to a file.

  96. Vault KV-v2: Why put Silently Wipes Every Sibling Field

    vault kv put wipes sibling fields and -cas fails with "check-and-set parameter did not match the current version": why, and how patch avoids it.

  97. Replacing Distributed SSH Keys with a Vault Certificate Authority

    TrustedUserCAKeys with the Vault ssh secrets engine: short-lived SSH user certificates limited by allowed_users and ttl instead of authorized_keys.

  98. Tuning Ceph Recovery: The Trade Between Degraded Time and Client Latency

    osd_max_backfills does nothing under mClock unless osd_mclock_override_recovery_settings is set. Choosing and measuring osd_mclock_profile for recovery.

  99. Capacity Planning Against the Fullest OSD, Not the Average

    Ceph full_ratio applies per OSD: one OSD at 95% stops writes while ceph df shows 70%. Alerting on the fullest OSD in ceph osd df, plus the upmap balancer.

  100. CRUSH Hybrid Rules: Pinning the Read Primary to SSD Without Rebalancing

    step take default class ssd, chooseleaf firstn 1, then class hdd firstn 0: a CRUSH rule with an SSD primary and HDD replicas, tested with crushtool.

  101. SMR Drives in a Replicated Cluster: Finding the Disk That Ruins the Pool

    lsblk ZONED=none does not rule out drive-managed SMR. A 30-minute fio randwrite probe and p99 latency ratio catch the cliff before it hits a Ceph OSD.

  102. A Layered Storage Benchmark: Isolating Device, Network and Protocol Bottlenecks

    fio, iperf3, rados bench, then fio on RBD: benchmarking device, network, Ceph protocol and full stack separately to find which layer caps throughput.

  103. Infrastructure as Code with Ansible: Making a Host Reproducible from the Repository

    ansible-playbook --check --diff and a changed=0 second run as proof a host matches its repository, with a container demo that corrects a hand edit.

  104. Multi-Stage Docker Builds: Shipping a Runtime Without the Toolchain

    COPY --from=build with CGO_ENABLED=0 and distroless/static: a multi-stage Dockerfile taking a Go image from 812MB to 24MB, and the no-shell trade-off.

  105. A Minimal Go HTTP Service with chi, Timeouts and Graceful Shutdown

    Go http.Server ReadHeaderTimeout and srv.Shutdown with signal.NotifyContext: draining in-flight requests on SIGTERM in a chi service, with a test.

  106. Managing a Shared DNS Zone Through a Replace-Everything API

    PUT /zone replaces the whole DNS zone: a read-merge-write client with owner TXT records that keeps unmanaged records and removes stale managed ones.

  107. Powering and Discovering Peripherals over PoE with Multicast Announcements

    IP_ADD_MEMBERSHIP multicast discovery for PoE sensors in Python: announcer, collector with expiry, and why IGMP snooping without a querier breaks it.

  108. Provisioning a Single-Board Computer as an Industrial Edge Node

    /proc/device-tree/model read by Ansible to load board-revision vars via include_vars with_first_found, with a generic fallback and a container test.

  109. A Python CLI That Scaffolds a Project Structure You Actually Want

    Python scaffolding CLI with only argparse and string.Template: generates src layout, pyproject.toml, Dockerfile and .gitignore, tested with tomllib.

  110. Replacing pip, venv, flake8, black and isort with uv and Ruff

    uv sync --locked and ruff check replacing pip install -r requirements.txt, venv, flake8, black and isort: uv.lock plus one pyproject.toml config.

  111. Configuration Precedence: Docker Secrets, Kubernetes Secrets, Environment, .env, Default

    Docker secrets in /run/secrets, Kubernetes secret mounts, env vars and .env: a Python resolver with fixed precedence that reports which source won.

  112. Closed-Vocabulary Pydantic Validation for a Public Ingest Endpoint

    Pydantic extra="forbid" and Literal types return 422 for typo'd fields and unknown values that the default extra="ignore" silently drops.

  113. An Exception Hierarchy and One Handler for FastAPI Error Responses

    FastAPI add_exception_handler on a base AppError instead of raising HTTPException in services: one status-code mapping and a consistent error body.

  114. Designing a Consistent FastAPI Response Envelope

    FastAPI response envelope with Pydantic Generic[T] and a custom APIRoute get_route_handler that wraps every response in success, data and error.

  115. A Feature-Module Layout for a FastAPI Application That Keeps Growing

    FastAPI APIRouter per feature directory with its own router.py, schemas.py and service.py, replacing a shared routers.py, schemas.py and crud.py.

  116. chmod -R and the setgid Bit: Why a Four-Digit Mode Is Not Enough

    chmod -R 775 clears the setgid bit of 2775 directories, so new files lose the shared group. Separate find -type d and -type f, and assert with stat -c %a.

  117. Docker Registry Credentials for Two Local Users on One Host

    "may require 'docker login'" for a deploy user after root logged in: credentials live in each user's ~/.docker/config.json. Per-user login with Ansible.

  118. Reasserting Kernel Sysctls That Docker Silently Reverts

    net.ipv4.ip_forward = 1 although sysctl.d says 0: dockerd rewrites it at start. A oneshot unit After=docker.service reruns sysctl --system.

  119. Deploying Docker Compose from Ansible Without Shelling Out

    community.docker.docker_compose_v2 instead of a command task running docker compose up -d: real changed status and --check --diff support.

  120. Ansible Role Idempotence: Why a changed=0 Run Is the Only Proof You Have

    Molecule "Idempotence test failed because of the following tasks": why command, shell and timestamped templates report changed, and how to fix it.

  121. Migrating the SSH Port Mid-Playbook Without Locking Ansible Out

    Ansible meta: reset_connection after changing the sshd Port: validate with sshd -t, flush_handlers, and ping before closing the old port.

The full text of everything here is in one file at /llms-full.txt.