All notes

Reproducible Static Builds Need Explicit Inputs

Version dependencies, clocks and network data, then reconcile the artifact actually deployed.

English edition, edited for length. Read the complete Chinese original.

With the same Git commit, the build was successful in the morning but failed in the afternoon because the remote image interface was no longer responding. Static sites move runtime tasks to the build phase, but do not automatically eliminate external dependencies. The first step in reproducing the build is to list all inputs, not just the source code.

This article is aimed at websites using npm and Astro. Achieving complete byte-for-byte consistency in builds is more demanding than the “functional consistency” requirement. Teams should first clearly define the specific guarantees they need, rather than pursuing complete file hash equality without a clear objective.

First, distinguish between the three types of guarantees needed

The “function reproducibility” indicator should produce the same page for the same input, while “byte reproducibility” should ensure that all output bytes are identical. The “traceability” indicator should allow you to determine which input was used in each deployment. For personal blogs, traceability and function reproducibility should be guaranteed. Whether byte-level consistency is required depends on the verification, distribution, and auditing goals. Time-based labels can cause expected byte differences, but random changes in the article collection indicate a functional problem. First, define your goals to distinguish between acceptable variations and hidden errors.

The complete input includes source code, content, dependencies, toolchain, environment configuration, time snapshots, remote data, and build commands. Only saving the Git SHA cannot recreate a site determined by a real-time CMS. The release manifest should be associated with non-sensitive versions or summaries of these inputs, rather than treating the credentials themselves as the means of recreation. Secrets are used to obtain the input, but the secret values should not be stored in a public archive.

Dependency Locking and Toolchains

npm ci
npm run check
npm run build

npm ci: Use a lock file and install under clear conditions, suitable for CI. The lock file should be committed along with the package.json file. Also, fix the Node major version and the package manager version to prevent changes in native dependencies, parsers, or default behavior. Do not treat the lock file as a temporary cache and ignore it arbitrarily.

Dependency locking does not equal dependency security. Older versions may contain vulnerabilities and require a controlled upgrade process and verification. The ability to reproduce the issue means that changes can be tracked, not that upgrades are never performed. Upgrade PRs should be observed separately from large-scale content modifications.

Lock file management relies on dependency resolution, but Node, package managers, operating systems, and native builds can still influence the outcome. Fixing the main version can reduce discrepancies, and strict scenarios require even more precise versioning and compatibility with the container environment. npm ci checks for consistency between the checklist and lock file under clean installation conditions, and should not be used to automatically modify the lock file when CI fails. Failure indicates inconsistent input, which should be fixed by explicitly updating dependencies.

The cache is solely for speeding up, not the sole source of trusted input. When the cache is corrupted or invalid, it should be possible to re-download and verify; building the cache requires correct keys and includes versions that affect the results. If the cache key only uses package.json, and old results are still used after the lock file changes, it may lead to installing incorrect dependencies. First, establish a baseline in a clean environment, then enable cache comparison to avoid mistakenly attributing random behavior in the toolchain to the cache.

Relying on upgrades to maintain independent changes, perform content validation, build, and output difference checks. Locking down older, vulnerable versions is no safer than controlled upgrades; simply making changes understandable. To address supply chain risks, source, permission, and integrity management are also necessary. Locking files are not a solution to all security problems.

Time is often overlooked as an input

The page writes new Date(), and the results are different each time. Scheduled publishing also selects different articles based on different build times. Instead of each component reading the system time independently, you can explicitly input the build snapshot time and record it in the publishing list.

If “Last Updated Time” only displays the current build timestamp, it could mislead readers into thinking the content has just been reviewed. It’s important to distinguish between the modification time, publication time, and build time of an article, and to record the source of each. The operating system file’s mtime should not be considered as a record of content history, as it will be changed by CI checkout.

Assume the build starts at 8:59 AM and finishes at 9:01 AM. If each generator reads its current time, the homepage might not have new articles, but the RSS feed will. To maintain consistency within this version, fix a specific time snapshot at the beginning of the build, and have all public outputs use it. This ensures that the version selection remains consistent. The scheduling decision determines when to start the build, and the time filtering decision determines which content is included. These two responsibilities should not be mixed within a single component.

When rebuilding historical versions, use the original snapshot to check what was publicly available at that time. Using the current time may introduce future scheduled content into older submissions. Separate records for build time and content time should be maintained, and old documents should not be automatically refreshed to the current time. The date format must include a clear time zone or calendar semantic, to avoid changing boundaries after the CI machine switches time zones. Test the impact of cross-day, leap day, and daylight saving time on input, and explain according to the actual time zone rules of the site.

Random identifiers also require either removal or explicit seeding. When sorting lists with identical items, a stable secondary key should be used. The order of file traversal should not be used as the order of the product. When serializing objects and generating resource lists, ensure consistent rules to minimize noise differences, making it easier for reviewers to identify the true changes between the main content and links.

Should Network Input Be Cached, Fixed, or Allowed to Fail?

When requesting CMS, fonts, or images during build, it’s crucial to specify the version and failure strategy. If required inputs are missing, the build process should be halted, and the previous deployment should be retained. Optional inputs can have a fixed rollback, but this rollback must be recorded. It’s not permissible to silently return an empty array and then claim success after clearing all site content.

While snapshots of remotely stored data, after verification, can improve build stability, they also introduce synchronization and copyright responsibilities. The order of API responses also needs to be explicitly sorted, otherwise the same dataset may produce different lists and RSS order. Random IDs and un-fixed sorting will also introduce inconsistencies in the output.

After obtaining data from CMS, save the version, timestamp, and integrity summary. Then, use the approved snapshot as input for the build. Updating the snapshot represents a content change, requiring verification of the number of items, paths, and fields, rather than automatically reading “latest” each time. If the business requires real-time reading, acknowledge that the build depends on the current external state and define rules for retaining the previous deployment in case of failure. Reproducibility and real-time capabilities are different requirements, and a clear trade-off is needed.

Remote images and fonts are treated the same way. Before hosting or caching, verify the license, ensure the source version is fixed, and record the converter parameters. If downloading fails during build, it should not automatically switch to unrelated assets, nor should it indefinitely wait. If necessary resources fail, the build should stop, and optional resources should be used with a verified fallback and the reason recorded. If all errors are ignored, the process should continue to generate an empty page, which can hide availability issues during successful deployment.

Snapshots may also contain private fields, storing previously filtered data while keeping private, raw data out of the public repository. Summaries of public content are used for comparison, but do not guarantee the accuracy of the information and still require review. When updating, save a list of changes to facilitate reverting to a previous known version, rather than repeatedly requesting a service that has already been modified.

Release Checklist to Ensure Accurate Build and Deployment

A minimal checklist records the submitted code, toolchain version, build snapshot, content input summary, output summary, and deployment identifier. It doesn’t need to be fully public; internal records are used for recovery, and the page only displays the necessary versions. After deployment, compare the actual entry with the version in the checklist to prevent successful uploads where the domain still points to the old application. Build reproducibility and deployment verification are complementary and cannot be used as a substitute for online checks.

When preserving artifacts, maintain the complete directory structure, and do not simply save HTML files while losing the associated hashes. Before reverting, ensure that the platform can switch to this version, and that caching policies do not continue to mix new and old references. The retention period for historical artifacts and the requirements for security updates need to be clearly defined: While older versions can be used for diagnostics, they do not mean that code with known vulnerabilities can be indefinitely re-deployed. The goals of restoring known content and restoring secure operation need to be evaluated simultaneously.

The comparison list can also reveal automatic publishing errors. If two articles are published but the output increases by zero, this indicates that the scheduling or conversion process has not met expectations. If a large amount of content is un-scheduled, this should be immediately blocked. The number of articles is just a signal; the final comparison should be made against specific tags, RSS feeds, and details. Tracking the process allows for a clear understanding of the scope of content deployed each time, rather than relying on the author’s memory of what should be present.

Comparing Products Without Revealing Secrets

When rebuilding in the same environment, generate a file list and hash. If the differences are only due to expected build identifiers, record the exception. If the entire page has changed, systematically investigate the time, network, or non-deterministic factors. Do not include all environment variables in the report solely for the purpose of comparison.

Product traceability can be achieved using commit SHA, build time, and deployment ID, but this metadata should also adhere to privacy and public disclosure requirements. Public pages do not need to expose internal CI addresses, while internal release lists can store complete associated information.

During acceptance, install and build from a clean directory, simulating a failed remote interface. Verify failure does not replace the production site. After retaining lock files, toolchain versions, input snapshots, and deployment records, rollback is the correct way to restore the known state, rather than attempting to re-establish the current dependency environment with old code.

Another often overlooked input is the working directory location. A plugin might write the absolute source path to the mapping or generated JSON. If the same commit is placed in /workspace/a and /workspace/b, it will result in different bytes, and potentially expose the local username. To create the same input using two independent directories, check for path prefixes in the differences. If the mapping truly requires the source location, prioritize using relative paths or remapping configurations supported by the tool, rather than blindly replacing strings in the final file.

Changing line breaks also changes the input bytes. The author’s machine, Git attributes, and copy tools may produce different text formats. If the content fingerprint is based on bytes, the conversion must occur before the explicit boundary of the fingerprint, not after approval. First, determine whether the main text requires consistent line breaks, then record the rules for rejecting or converting. Then, compare the actual content copied from the code blocks. The fact that the content is the same does not mean that the same bytes are used in the executed command.

When archiving, it’s important to distinguish between the file content and its metadata. Two identical compressed archives may have different summaries due to variations in the order of items or timestamps; if the goal is to verify the deployment directory, it’s necessary to save the directory listing and the summaries of each file, and any differences in the packaging format should be explained separately. If the goal is to reproduce the archived bytes, then fix the packaging tool, sorting, and timestamping strategies. Don’t just write “hashes are different” in the report without specifying whether you are comparing HTML files, a directory listing, or an uploaded package.

Specify the Source Version for Each Input

The source code can be located using a commit identifier, dependencies can be identified using lock files and the toolchain, and remote data requires independent snapshots or vendor versions. List these items in an input inventory, specifying the method of acquisition, whether they will change, and how to check their integrity and what to do if they are missing. Simply noting “calling CMS” does not guarantee that historical content can be reconstructed; you must know which version of the response was used. Similarly, simply noting the Node main version does not guarantee that the byte values are exactly the same; you must specify the necessary level of precision.

The input list should not contain secret values. The token determines which data can be retrieved, and the allowed public snapshot is used to construct the content. If the original server response contains private fields, project and archive them first, and do not include the entire JSON in a public repository under the pretext of “reproduction required.” Original, controlled data and publicly available snapshots are two distinct assets, each with its own permissions and retention rules. The ability to reproduce content should not violate the boundaries of content confidentiality.

When dynamic external input cannot be reliably fixed, it’s crucial to ensure that commitments are trackable, rather than pretending that the same commit can produce the same site. The release records should store the actual read version, timestamp, and summary, so that maintainers can understand the differences originate from external data. When the network fails, keep the old deployment, and avoid silently creating an empty set. The optional rollback feature should have a clear version and notification, to avoid the meaning of “success” changing depending on the network state.

Building Location with Two Isolations

Prepare two clean working directories, with a fixed input list, a snapshot of the build, and a toolchain. Run the same commands and then compare the sets of paths and file summaries. First, check for new and deleted files, then check for changes in files with the same paths. The absence of a file in a directory indicates a problem with the scope of functionality, while changes in file timestamps may be acceptable. Do not ignore the entire output directory at once; instead, explain the differences item by item and retain only the minimum exceptions.

If the differences arise from order, check the file traversal, remote array sorting, and the secondary keys of identical items. If they originate from random IDs, verify that they maintain a consistent identity, and replace them with stable content identifiers if necessary. If the differences stem from font or image conversion, check the compatibility between the native library and the system environment. Layered localization can be more effective than repeatedly rebuilding, allowing for the creation of a minimal sample that can be consistently used for future upgrades.

Structured comparison can help understand HTML differences, but it cannot automatically remove all byte-level changes that appear similar. For byte-level reproducibility, the target files must be identical, and the functional consistency target also requires defining which structures and content can vary. The experimental report separately describes the target and results, and does not claim that the DOM is completely identical based on byte-level comparison. Source mapping and resource references are also listed, and cannot be compared only based on the homepage.

Building a Cache Requires the Ability to Make It Expire and Regenerate It

The cache key should cover the input that determines the cache result, rather than any arbitrary version of the project. The dependency graph for installation caching differs from that for article rendering caching: the former focuses on lock files and toolchains, while the latter focuses on content, templates, plugins, and cleaning rules. Using a single short key for all caches can easily lead to pollution, while adding timestamps to the key would lose its reusability. A clear dependency graph is necessary to choose meaningful keys.

When the cache is unavailable, it should be possible to reconstruct the data from a trusted input. Cache hits should also be validated with necessary integrity and state checks. Old rendering results, even if unchanged, should not be used after a security fix. Version conversions represent output semantics. The test should first clear the cache to establish a baseline, then enable cache comparison, and finally, invalidate a dependency check. The test should only verify normal hits and should not be sufficient to prove that there are no errors after the update.

During the recovery process, do not temporarily change the installation command from “ci” to “install” in the CI configuration to “fix” the build. This could alter the lock files and parsing results, leading to a new environment without a corresponding manifest after the incident. Necessary dependency upgrades should be treated as explicit changes, and re-checked and archived. The caching acceleration process does not grant you the authority to determine the correctness of the input.

Verifying the Actual Deployed Object When Saving the Output

The directory structure is correct, but the deployment command uploads to a different directory, which could still result in an older version being deployed. The release manifest should record the output path, summary, and deployment identifier, and verify the public version and key content in the actual entry point. Simply receiving a “success” message from the tool does not prove domain ownership or that the cache has been switched. Both require evidence. We need to be able to reproduce the answer and verify what the current answer provides.

The artifact archive includes HTML, hashed resources, and necessary configuration, maintaining directory relationships and access permissions. The retention period considers recovery needs and security updates. Older artifacts, which may contain known vulnerabilities, can be used for investigation, but should not automatically be the preferred rollback option. Artifact summaries help verify integrity, while the source’s trustworthiness and review records remain independent. A correctly hashed malicious artifact can also be precisely reproduced.

Practice Failure Scenarios in an Isolated Environment

Simulate remote input timeouts, file locking inconsistencies, and content validation failures. The expected outcome is that the new deployment does not switch over and the previous version’s entry remains accessible. Use a temporary directory and a synthetic endpoint without deleting production dependencies or modifying real data. Record failure reports, including the stage, category, and related input versions, while removing credentials and full text content. Ensure that failure checks do not leave behind a partially completed directory that could be mistakenly used in subsequent deployments.

Finally, restore normal input, confirm that the process can be rebuilt and deployed successfully, and that there are no empty results when the reuse fails. The rollback exercise also verifies RSS, article scope, and resource references, not just the homepage title. Each result corresponds to the input list and the output version, allowing maintainers to restore a known, complete, and safe site, rather than relying on the current network state.

Content sorting should not rely on the machine’s default language environment.

A single directory can contain titles in English, Chinese, and with accented characters. The generator compares titles based on the default locale. When two build machines use different language environments, the order may vary. Pagination boundaries and RSS entries also change accordingly. For permanent machine identifiers, a clear and deterministic comparison rule can be used. For user-facing language sorting, a clear locale and related options should be selected, and the required toolchain support should be included in the input constraints.

Establish a fixed set of samples containing variations in capitalization, accents, numbers, and identical titles. Examine the final position of each item. When items are equal, use a stable identifier as a secondary key to prevent the file reading order from determining the result. The test should compare against a complete, expected list, not just asserting the first title. In pagination scenarios, also compare the boundaries between adjacent pages to prevent the overall count from changing even when the content is split across multiple pages.

This doesn’t mean that all directories must use the same sorting method. The list of articles can be sorted by publication date, the dictionary by language rules, and the internal product list by stable paths. The key is that each rule is explicitly defined and consistent with its intended purpose. If the language sorting for products is changed to a different set for byte consistency, it should be treated as a change in behavior and reviewed, rather than quietly “fixing” it within the build script. The project should use the actual supported toolchain to verify its guarantees, rather than running different ICU environments.

Rollback requires the use of validated products

Imagine a previous version that relied on a remote CMS. After an incident, the previous version was rebuilt, but it read the data that had been modified since. This did not restore the old site. A more reliable rollback strategy involves saving and verifying complete deployment artifacts, and using a release manifest to indicate which commit and input snapshot they correspond to. The storage of these artifacts should also include retention periods, integrity checks, and access control, and should not allow the storage of artifacts containing private debugging information to be made public.

When practicing, don’t just click the “revert” button. Instead, check the article list, RSS feed, resource hashes, and the main content of key pages to ensure they have reverted to the expected version. If, after reverting, there are new HTML references to new resources, it indicates that the cache or version switching is incomplete. Record this discrepancy, fix the process, and then include the revert capability in the release commitment.

References: npm ci, Reproducible build.