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:
- Created a new
IOThroughputChart.jscomponent to display I/O bytes over time - Rewritten
ClusterTopology.jsto visually distinguish S3 frontend proxies (blue) from Kuri storage nodes (green) with gradient backgrounds, icons, and status rings - Rewritten
Cluster.jswith a two-column grid layout for better data presentation - Updated
Cluster.csswith responsive breakpoints and refined styling - Updated
ClusterTopology.csswith new visual elements These were all source files in the React application. But the Docker build copies from the compiled output directory (build/), not the source directory. The assistant had not runnpm run buildsince making these frontend changes. The Docker build at message 796 would therefore produce an image containing the old React build artifacts — the pre-change frontend — paired with the new Go binary. The assistant's implicit assumption was that the Docker build would somehow capture all changes, including frontend modifications. This is an easy trap to fall into in a monorepo where a single Docker build command appears to "build everything." But in this project architecture, the React build was a separate step that needed to be executed explicitly before the Docker build.
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:
cd integrations/web/ribswebapp && npm run build— compiles React source into static JS/CSS/HTML inbuild/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:
- Compiled Go (
go build ./...) — success - Built Docker image — success
- Restarted containers — success
- Verified backend RPC responses — success (live stats appeared)
- 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 buildand 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:
- Docker multi-stage builds: Understanding that Docker build stages are isolated and that the final image only contains what is explicitly copied
- React build pipeline: Knowing that React/JSX source code must be compiled into static assets via
npm run build(or similar) before deployment - The project's Dockerfile structure: Specifically, that the Docker build copies pre-built frontend assets rather than building them from source
- Container orchestration: Understanding that
docker compose up -d --force-recreatereplaces running containers with new image versions - The three-layer architecture: Knowing that S3 proxies, Kuri storage nodes, and the web UI are separate services with separate build artifacts Without this knowledge, the Docker build at message 796 appears to be a routine, unremarkable step. With it, the message becomes a case study in build pipeline complexity.
Output Knowledge Created by This Message
The message itself produced a Docker image with SHA 74b9d85dea286ce03a07f4ba306632c543550353d4bccfe5a54a6b4a809bc426. This image contained:
- The updated Go binary with live storage statistics in the
ClusterTopologyRPC - The old React frontend without the I/O throughput chart, layout improvements, or visual role distinctions The image was immediately usable for backend verification (which the assistant performed successfully) but was incomplete for frontend verification. The output knowledge created by this message is therefore twofold: a tangible artifact (the Docker image) and an intangible lesson (the build ordering dependency).
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.