svc.nz
Blog
Small snapshots · 5 min read

How do I keep versions of a config file or small dataset without setting up git?

Store each snapshot under the same object name in a versioned byte store, read the newest version when you need current state, and pin a numbered version for reproducible jobs. This gives you rollbackable history without pretending to provide Git's review and merge model.

30 August 2026 · svc.nz

Git is excellent when the thing being changed is source code or a text file that benefits from branches, diffs, review, and merges. It can be unnecessary for a small JSON configuration, a generated lookup table, a nightly measurement, or a model prompt that one process publishes and another consumes. Those cases need snapshots, not a developer workflow.

The basic model is append-only versioning. A writer sends a complete representation to one stable name. The store keeps the old bytes and assigns a new version. Readers can ask for the current version or pin a specific one. That last property matters when a deployment must be repeatable: “latest” is convenient for a live worker, while “version 12” is an auditable input.

Choose the right history model

Before choosing a tool, decide whether you need a line of snapshots or a collaborative history. If several people edit overlapping text and need review, use Git or a configuration platform. If one producer publishes whole files and readers need reliable retrieval, object versioning is a better mental model. If you need queries over rows, use a database or dataset system rather than treating a blob as a database.

A config file's version line Three PUTs create versions one, two, and three. A latest GET reads version three, while a pinned read can retrieve version one. v1 v2 v3 PUT baseline PUT change PUT current latest points to v3, a pinned read can point to v1
Each write preserves the earlier representation. A rollback is another deliberate write, not an invisible overwrite.
Need Versioned object approach Git approach
Whole-file snapshots Simple PUT and GET Works, with repository overhead
Current value Read latest Read the chosen branch or commit
Exact reproducibility Pin v<n> Pin a commit
Review and merge Build separately Native workflow

Use a stable name and explicit versions

Name the object after its role, not its current value. A descriptive name such as config.json keeps the purpose clear without implying a final revision. A stable name lets consumers use one address. The version metadata tells operators what was read. Put a release ID, schema version, and timestamp inside the config too. Storage version numbers alone do not explain why a change was made.

curl -X PUT --data-binary @config.json \
  https://in.svc.nz/team-config/app.json \
  -H 'Authorization: Capability YOUR_WRITE_CAPABILITY' \
  -H 'Content-Type: application/json'

curl --fail --output app.json \
  https://out.svc.nz/team-config/app.json/latest \
  -H 'Authorization: Capability YOUR_READ_CAPABILITY'

Never put the capability in the URL. Keep it in an Authorization header, and load it from a protected environment or secret store in automation. The URL is safe to record in deployment metadata when the capability is not embedded in it.

Validate before rollout

A versioned file can still be wrong. Validate syntax, required keys, allowed values, and semantic relationships before publishing it. For a production rollout, use a staging consumer or a separate validation job. A consumer should reject an invalid object rather than silently falling back to an older version unless that fallback is an explicit policy.

Use a digest when the handoff crosses trust boundaries. A digest lets the receiver confirm it has the intended bytes, but it does not make the bytes authentic by itself. The read capability and TLS protect access in transit, while a signed release manifest can provide stronger provenance when the threat model requires it.

Rollback without rewriting history

Suppose version 7 breaks a worker. Fetch a known-good version, test it, and PUT it as the next version. The latest object now contains the restored content, and the history shows that a rollback happened. Do not delete the bad version merely to make the history look tidy. It may be useful evidence when diagnosing the change.

There is a subtle concurrency issue. If two writers publish to the same name, the service sees two versions, not a merge conflict. Give each writer a clear ownership rule, or use a coordination record with a compare-and-set mechanism provided by your chosen system. A storage primitive cannot infer which business change should win.

How a byte store fits small config workflows

svc.nz stores arbitrary bytes in named boxes. PUT to in.svc.nz/<box>/<name> creates a new version. GET from out.svc.nz/<box>/<name>/latest reads the newest version, and a version path can pin an earlier snapshot. A capability sent in the header gives the writer or reader only the access you choose.

This is useful for a script-generated config, a handoff between CI and a deploy job, or a small dataset shared with an agent. Optional client-side encryption can protect content from the storage reader. Anonymous boxes expire, so use a durable, managed system when the data must be retained under a formal policy. Hosted access remains an invite-only private trial with limits described in the policy centre.

What it does not provide

Versioned storage leaves rollout decisions to your application. It does not decide which fleet should adopt a value, enforce approvals, calculate a diff, or understand the schema. It is not a relational query engine, and it does not resolve concurrent edits. Those responsibilities belong in the producer, consumer, or a more specialised tool.

This separation leaves data interpretation to your application. The service can treat JSON, CSV, text, and binary data as opaque bytes. Clients choose conventions that fit their environment. A small producer can be written with curl, while a larger system can wrap the same HTTP shape in a job runner.

FAQ

Is this a replacement for Git?

A byte store suits immutable snapshots and retrieval. Git remains the better choice for branching, code review, merging, and line-level history.

How do I read the exact config used by a job?

Record the version returned by the PUT or GET response, then fetch that version rather than latest.

Can I keep only the last few versions?

Retention depends on the service and policy. Design an explicit retention rule, and do not assume that deleting a pointer proves every underlying copy is erased.

Should secrets live in the config file?

Prefer references to a secret manager. If a secret must be transported, apply the separate secret-sharing and client-side encryption controls described in this blog.

Sources

Git documentation, repository basics; RFC 9110, PUT semantics; MDN, HTTP GET; NIST SP 800-57, key management.

Related: resume an interrupted upload → ← Previous: share a secret Next: client-side encryption →
svc.nz Notes for the private trial. Operated by Anphase Ltd in Aotearoa New Zealand.
Policy centreTermsPrivacyAcceptable useAbuseSecurityCopyrightCookiesSubprocessors