What breaks without thoughtful object storage configuration
Object storage is connected quickly: create a space, get access keys, put static assets, exports, and backups there.
A few months later it turns out that files are served noticeably slower, some requests time out, and nightly synchronization increasingly fails to finish by the start of the workday.
The problem is almost never in the service itself — DigitalOcean Spaces configuration simply remained at the "created and forgotten" level.
The first bottleneck is access. When static assets are served directly from the same space where exports and backups are written, client requests compete with bulk write operations.
As the number of simultaneous requests grows, we hit the connection ceiling: the queue grows, file serving time increases, some requests fail with an error, and integration contractors receive unstable API responses.
Second — lifecycle rules. Without them, every release, every data snapshot, and every intermediate archive stays in the space forever. Volume grows, listing objects takes longer and longer, and full backup no longer fits into the nightly window.
This is where synchronization tasks most often fail: some objects are transferred, some are not, and copy integrity has to be checked manually.
Third — separation of permissions. One shared set of keys for all services means that a leak of any of them opens access to the entire structure, including backups and user data.
Next we break down how this is addressed: separation of spaces and roles, a separate serving layer, lifecycle rules, integrity checks, and error monitoring at every stage.
DigitalOcean Spaces: configuration for real project tasks
Object storage covers what fits poorly on the application server file system: DigitalOcean Spaces provides an S3-compatible interface, so uploading and serving files are described by standard library calls rather than custom code.
We separate public and private by buckets: static assets and media in the public one, user documents in the private one, access to them via links with a limited lifetime.
CORS policies, object lifecycle rules, and versioning are configured once and then work without developer involvement.
Speed of serving static assets and media
Files are served by the content delivery network, not the application server: the browser receives an image or video from the nearest node, and the web server does not spend CPU time and bandwidth on serving.
We separate caching headers by content type — immutable interface builds are cached for a long time, user uploads are updated more often so as not to show outdated content. For video we enable range requests: the player loads the needed fragment instead of the whole file.
Fault tolerance and volume growth without migration
Data is stored with redundancy within the region, versioning protects against accidental deletion and overwriting, and lifecycle rules remove old copies and incomplete uploads — storage does not grow uncontrollably.
When volume grows severalfold, the architecture does not need to change: the same access key, the same address, and the same code continue to work. We put database backups in a separate bucket so that recovery does not depend on the availability of the main server.
Options for connecting object storage to a project
Object storage is connected to a project via the S3-compatible API of DigitalOcean Spaces, and there are few access options — they differ in the permission model and the point from which the file reaches the user.
The mode determines who reads the object: the browser directly, an intermediary server, or the content delivery network. Below are working combinations and limitations of each.
Public and private containers
| Option | When it fits | Limitations |
|---|---|---|
| Public container | Serving static assets and media: images, fonts, public documents | Anyone who knows the object address can read it; listing has to be closed by policy, there is no per-user separation |
| Private container + temporary signed links | User files, exports, role-based access | The link lives for minutes, long browser caching is impossible, each signature is calculated on the backend |
| Private container + serving through the server | Need download accounting, permission checks, antivirus | Traffic goes through the application: load and outbound traffic volume grow |
| Archive container | Backups, rarely read exports, archival file storage | Longer first byte, some operations unavailable, recovery requires warming |
Serving files through the content delivery network
Static assets and media are served through the content delivery network: the container remains the origin, and the client receives the file from the nearest cache node.
Storage and server are offloaded, but a requirement appears for address versioning — otherwise an updated file is still served in the old version for another day.
The working scheme: immutable object names plus a short cache on index files, so that rollback and publishing do not depend on manual cache purging.
Implementation order: from audit to handover to the team
We start with inventory: what is stored now, how services access this data, and what is critical for rollback. Without such an audit, migration to S3-compatible object storage turns into moving files around with loss of relationships between objects. Next is a six-stage route that our team follows.
Audit of current storage and data schema
- Storage audit. We build a map: volumes, object types, who reads them and by which key. Separately, we find "orphans" — files that no one requests but that continue to take up space.
- Container schema. We split data by buckets: public assets, user uploads, backups. For each, we set key naming rules and lifecycle policy so that storage does not grow uncontrollably.
- Access rights setup. We separate keys by service with the minimum required permissions, close public access where it is not needed, and restrict requests by source.
Configuration, verification, and handover
- Data migration. We copy objects while preserving metadata and key structure, synchronize changes accumulated during migration, and leave the previous storage ready for rollback.
- Load testing. We run real read and write scenarios: parallel uploads, connection interruption, resending the same object. We look at where response time grows.
- Documentation handover. We provide the bucket schema, permission matrix, lifecycle rules, and rollback instructions — so the team can change the configuration without us.
What we hand over: access policies, schemas, procedures
We assemble the documentation set so that the storage can be maintained and reconfigured without us: configurations, schemas, and procedures are kept in the customer's repository and versioned together with the code. Below is what exactly remains in hand after DigitalOcean Spaces configuration.
Access policies and object lifecycle
- Access policies: containers are closed by default, publication is served only to the serving endpoint; permissions are granted at the minimum sufficient level, with separate keys for each environment and service.
- Container schema: naming by environment and purpose, prefixes for build artifacts, media, and backups — so that permission analysis and object search take minutes, not hours.
- Object lifecycle: rules by age and prefixes, transition to a cold class, deletion of incomplete multipart uploads, and limiting the number of stored versions.
- Role and access matrix: which services and people read and write what, with keys and boundaries of their action.
- Serving schema: linking the container to an accelerating cache, caching rules, and invalidation order when content is updated.
Procedures and instructions for the team
- Backup procedure: what is copied, how often, copy retention period, and the procedure for verifying recovery on a test container.
- Instructions for the team: connection from the application and from the build agent, artifact upload, key rotation, actions in case of serving failure or loss of access.
- Emergency recovery and rollback procedure: the point from which we bring up data, and integrity verification steps after return.
We verify the set in practice — we bring up an environment from scratch according to these documents on a separate account. Everything that had to be figured out along the way, we add to the procedure: the customer receives not a folder of files, but a working procedure.
Case study: accelerated report exports and eliminated synchronization failures
The client is a reporting service: several hundred users downloaded slices daily, and generation ran on a single application node. Files were written to the local disk and served for download from there — as volumes grew, the volume filled up faster than cleanup could run. We approached this as separating hot and archival data in DigitalOcean Spaces.
What it was before
By the end of the workday, the local volume was 85–90% full, and nightly generation failed with a write error if cleanup did not finish in time.
Synchronization with the external contour failed 12–18 times a month: each failure meant a manual restart and incomplete exports for users. A large report took about 40 minutes to generate because the node simultaneously read files, wrote new ones, and served completed ones.
What changed after
Exports for the last day remained on the fast local volume, and the archive moved to object storage.
The lifecycle rule keeps the latest versions of an object, and the rest goes to a cold class: the registry does not grow endlessly, and rollback to a previous report version remains possible.
Download goes directly from storage via a signed link — the node no longer spends bandwidth and memory on serving files.
Report generation — from 40 to 12 minutes, synchronization failures — from 12–18 to 0–1 per month, local volume usage — consistently up to 55%. The class of failures due to disk overflow has been retired from operation: during the observation period there were no such incidents.
How object storage differs from a file server
A file server provides POSIX semantics: the application mounts a network disk, appends bytes at an offset, places locks on a file, and lists directories.
Object storage is structured differently — a flat key space, an object is written in full, and access goes through a network software interface. Everything else follows from this difference: both the scenarios where each model wins and the classes of tasks where it breaks.
The object model wins on large volumes of uniform data: replication between sites, object versioning, lifecycle rules for old versions, serving static assets through edge caches, and access via signed links with a limited lifetime.
In these tasks, a network disk runs into the throughput of a single node, latency on many small operations, and a single point of failure — it has to be scaled up, not out.
Object storage loses where partial overwriting, locks, and millisecond response to small operations are needed. Working directories, database files, intermediate render results, and everything that is opened for simultaneous read-write remain on the file server.
The practical criterion is simple: if the application writes at an offset, it is a network disk; if it puts a complete file as a whole, it is object storage.
The transition is done through an intermediary layer that accepts old file paths and maps them to keys: the application continues to work as before, and the storage changes underneath it.
Directory listing is replaced by an index in the database, then a background copy transfers data with checksum verification, reads are switched by a flag by file groups, and the old server remains in read-only mode. Reverse rollback — with the same flag, without deploying a new version of the product.
DigitalOcean Spaces provides an S3-compatible interface, so utilities, libraries, and gateways already exist, and lifecycle policies keep the registry size under control.
Describe which part of the system is tied to file paths — we will come back with a migration plan and estimate.
Frequent technical questions about object storage
Doubts about object storage are almost always the same: will archive migration survive a workday, where will quotas start to hit, can a contractor be given access to only one prefix, and what will happen to the application code. Below are short answers to each point, which is usually how design begins.
Limits and rollback
How to migrate an archive without downtime?
Storage is connected as a second source of truth: first, background synchronization of existing data runs, then writing to both places is enabled, after checksum verification — reading is switched and the old contour is disabled.
A reverse pass through the log catches up changes accumulated during migration, so the downtime window is measured in seconds, not hours.
Where do limits and quotas begin?
The restrictions concern not the total archive volume, but the speed of operations: the number of requests per second per prefix, the size of a single object, and the number of simultaneous connections.
In practice, the bottleneck is not the storage but small files — a million objects of several kilobytes each hit limits earlier than terabytes of large exports. Therefore, large files are uploaded in parts in parallel.
How to grant access by roles and addresses?
Keys are issued for a specific task and restricted by policy: read-only, write-only, only its own prefix in the bucket. Additionally, access is closed by a condition on the address range — a key leaked from a service configuration is useless from someone else's network.
Separate keys for build, analytics, and backup allow one to be revoked without touching the other processes.
Is object versioning needed?
Yes, if there is a risk of overwriting: versioning saves every revision of an object, and a lifecycle rule deletes outdated copies by age or keeps the last few.
This way, rollback to a previous file version takes minutes, and the bucket does not grow endlessly due to old revisions.
What to change in the application code?
The service works via an S3-compatible API, so the file access library remains the same — the endpoint address and keys change. It is worth checking error behavior: retries, resuming after a connection interruption, and handling a missing object. These scenarios are covered on a test bucket before switching production data.
Let's discuss the storage task and come back with an estimate and plan
To make the estimate substantive rather than "plus or minus," it is useful to gather several inputs for the conversation.
We will collect some data ourselves when analyzing the current state, but what is already at hand noticeably shortens the time to prepare a proposal.
- Current data volume and profile: how many objects, average and maximum file size, what share is rarely requested data.
- How objects get into storage: upload from the application, regular exports, migration from another storage or from local disks.
- Who and how reads files: public access, links with a limited lifetime, access only from the internal network.
- Storage and deletion requirements: object lifetimes, versioning, recovery after accidental deletion.
- Access and environment isolation: whose accounts are used, whether separate spaces are needed for development, test, and production, whether there are requirements for access logs.
- Integration points: services that already work through the object interface, and what will have to be adapted.
- Load benchmarks: peak periods, file serving volume, acceptable response time during export.
Then we work like this: analyze the current state, assemble the data placement schema and lifecycle policies, estimate the scope of work and risks.
We come back with a staged implementation plan, timelines for each, and a schema that shows what is stored where and how access works. Describe the storage task — we will come back with an estimate and plan.







