The Silent Build: How a Docker Rebuild Exposed a Frontend Deployment Gap in a Distributed S3 Monitoring System

In the course of building a horizontally scalable, three-layer S3 architecture with real-time cluster monitoring, a single message stands out for what it reveals about the assumptions embedded in modern containerized development workflows. The message, appearing at index 796 in a lengthy coding session, is deceptively brief:

[assistant] Now rebuild and restart: [bash] cd /home/theuser/gw && docker build -t fgw:local . 2>&1 | tail -5 #20 exporting layers #20 exporting layers 0.8s done #20 writing image sha256:74b9d85dea286ce03a07f4ba306632c543550353d4bccfe5a54a6b4a809bc426 done #20 naming to docker.io/library/fgw:local done #20 DONE 0.8s

On its surface, this is nothing more than a successful Docker image build — a routine step in any containerized development cycle. But this message sits at a critical inflection point in the session. It represents a moment where a subtle but consequential assumption went unchallenged, leading directly to a user-reported gap between what was coded and what was actually deployed. Understanding why this message was written, what the assistant believed to be true at that moment, and what went wrong afterward reveals deep insights about the complexities of orchestrating multi-service container architectures, the separation of build concerns in web applications, and the invisible pitfalls that lurk in even the most straightforward deployment commands.

The Context: A Cluster Monitoring System Takes Shape

To understand the significance of this Docker build, one must first understand the extensive work that preceded it. The assistant had been building a comprehensive cluster monitoring dashboard for a distributed S3 storage system built on the Filecoin Gateway platform. The architecture was three-tiered: stateless S3 frontend proxies (running on port 8078) that routed requests to Kuri storage nodes, which in turn stored data in a shared YugabyteDB backend. The monitoring system needed to track throughput, latency, error rates, active connections, and I/O bytes across all nodes in real time.

The immediately preceding messages show the assistant deep in implementation work. At message 790, the assistant observed that the storage node statistics in the cluster topology were showing zeros for storage usage, objects count, and other critical metrics. The response was to update the ClusterTopology backend in rbstor/diag.go to populate real storage statistics when querying the local node. This involved reading GroupStats from the storage engine and mapping fields like TotalDataSize (which turned out to be named TotalDataSize, not Total — a naming mismatch that triggered an LSP error that was promptly corrected). At message 794, the edit was applied successfully. At message 795, the Go code was verified to compile cleanly with go build ./....

Then came message 796: the Docker build.

The Reasoning: Why Rebuild Now?

The assistant's reasoning at this point was straightforward and, on its face, entirely logical. The Go backend code had been modified — specifically the ClusterTopology function in diag.go — to include live storage statistics. The Go code compiled successfully. The next step in any containerized workflow is to rebuild the Docker image so that the running containers incorporate the new binary. The assistant's stated intent — "Now rebuild and restart" — reflects a standard development loop: edit, compile, build image, restart containers, verify.

But there was a critical subtlety. The Docker image for this project, tagged fgw:local, was a multi-stage build. The first stage compiled the Go binaries. The second stage copied those binaries into a runtime image along with configuration files and — crucially — the pre-built React frontend assets from integrations/web/ribswebapp/build/. The Docker build command does not trigger a React rebuild; it only copies whatever static files already exist in the build/ directory at the time the Docker build runs.

This is where the assumption crept in.

The Assumption: That Docker Captures Everything

The assistant had made extensive frontend changes in the same session. At messages 775–780, the assistant had:

The Fallout: User Reports Missing Changes

The consequences of this assumption became visible almost immediately. After the Docker build completed, the assistant proceeded to restart containers and verify the backend changes. At message 798, the ClusterTopology RPC correctly returned live storage statistics — storageUsed: 18747088559 (about 18 GB) and groupsCount: 1. At message 799, after generating traffic, requestsPerSecond: 2 appeared. The backend was working perfectly.

But at message 802, the user reported: "Latency chart: Rename SLA to SLO, set at 350ms. Don't see layout fixes nor IO chart, not deployed?"

This was the moment of revelation. The user could see the backend metrics (because the Go binary had been rebuilt and redeployed) but could not see the frontend changes (because the React app had not been rebuilt). The assistant's response at message 803 shows the realization: "You're right, let me check if the frontend was actually rebuilt and deployed. The Docker build copies the pre-built React app, so I need to rebuild the React app first."

The assistant then ran npm run build, which produced a new set of compiled assets. But even then, the Docker image still contained the old frontend. The assistant would need to run the Docker build again after the React build to produce an image with both the new Go binary and the new React assets.

Deeper Analysis: The Build Pipeline Architecture

This incident reveals the architecture of the deployment pipeline and the separation of concerns between backend and frontend builds. The Dockerfile for this project likely looked something like this:

# Stage 1: Build Go binaries
FROM golang:1.22 AS builder
COPY . /app
RUN cd /app && go build -o s3-proxy ./cmd/s3-proxy
# ... other Go binaries

# Stage 2: Runtime image
FROM alpine:3.19
COPY --from=builder /app/s3-proxy /app/
COPY --from=builder /app/gwcfg /app/
COPY integrations/web/ribswebapp/build/ /app/webui/

The critical line is the last one: COPY integrations/web/ribswebapp/build/ /app/webui/. This copies the pre-built React application. The Docker build does not and cannot run npm run build because that would require Node.js to be installed in the builder stage, which it isn't (the builder stage is a Go image). Even if Node.js were available, the Docker build would need to install npm dependencies and compile the React app, adding significant time and complexity.

The correct workflow is a two-step process:

  1. cd integrations/web/ribswebapp && npm run build — compiles React source into static JS/CSS/HTML in build/
  2. docker build -t fgw:local . — builds the Docker image, copying the already-compiled frontend assets The assistant performed step 2 without step 1.

The Thinking Process: What Was Visible in the Reasoning

The assistant's reasoning, visible in the messages leading up to and following the Docker build, shows a pattern of focusing on the backend verification loop. After making the Go changes, the assistant:

  1. Compiled Go (go build ./...) — success
  2. Built Docker image — success
  3. Restarted containers — success
  4. Verified backend RPC responses — success (live stats appeared)
  5. Generated traffic to test metrics — success Each step in this backend-focused loop succeeded. The assistant was operating in a "backend verification" mental model, checking that the Go code compiled, that the Docker image built, that containers started, and that RPC responses contained the expected data. The frontend was out of sight and out of mind. When the user reported the missing frontend changes, the assistant's thinking pivoted immediately. The phrase "let me check if the frontend was actually rebuilt and deployed" shows the assistant retracing the deployment pipeline and identifying the missing step. The correction was quick — the assistant ran npm run build and then, presumably, rebuilt the Docker image again — but the incident highlights how easy it is to lose track of build dependencies in a multi-language, multi-stage containerized project.

Input Knowledge Required to Understand This Message

To fully understand what happened at message 796, one needs knowledge of:

Output Knowledge Created by This Message

The message itself produced a Docker image with SHA 74b9d85dea286ce03a07f4ba306632c543550353d4bccfe5a54a6b4a809bc426. This image contained:

The Broader Lesson: Build Pipeline Visibility

The incident at message 796 is a microcosm of a broader challenge in modern software development: build pipeline visibility. When a single command like docker build abstracts away multiple underlying steps, it becomes easy to lose track of which artifacts are current and which are stale. The assistant's mistake was not a lack of competence — it was a natural consequence of working in a complex, multi-service architecture where the build pipeline is not immediately visible from any single vantage point.

The fix was straightforward once the gap was identified. But the incident serves as a reminder that in containerized web applications, the separation between frontend and backend build steps must be explicitly managed. Whether through Makefiles, CI/CD pipelines, or pre-commit hooks, the ordering of build steps matters. A Docker build is not a magic "build everything" command — it builds only what its Dockerfile tells it to build, and if that doesn't include a React compilation step, the frontend will silently fall behind.

Conclusion

Message 796, in isolation, is four lines of build output. But in context, it is a window into the invisible complexity of modern software deployment. The assistant's assumption that a Docker rebuild would capture all changes was reasonable but wrong, and the gap between expectation and reality was only revealed when the user reported missing frontend features. The message teaches us that in distributed systems, even the simplest commands carry hidden dependencies, and that build pipeline hygiene — knowing exactly what each command produces and what it leaves behind — is essential for reliable deployment.