Why object storage backups need their own plan in GitLab
If your self-managed GitLab instance keeps job artifacts, Git LFS objects, uploads and container registry blobs in S3, Google Cloud Storage or Azure Blob Storage, a backup archive from the GitLab backup command will not hold all of your data. By default, that command skips these object-stored data types, so the archive alone leaves gaps in artifacts, LFS, uploads and registry content. Those gaps must be closed with your storage provider's versioning and replication, and each data type needs a named owner, a documented recovery path and a restore test. Read the GitLab backup and restore documentation before you size the work, because the exact skip behavior and the options to include object storage depend on your GitLab version and how each store is configured.
This topic matters most to the platform team that runs the instance, the backup owner who signs off recovery objectives, and the compliance lead who must show evidence that development records survive an incident. It matters most when artifacts drive release or audit evidence, when LFS holds large binary assets that cannot be rebuilt from Git history, or when registry images are the only copy of a deployed build. Teams that have only ever tested restoring the archive often discover this gap during their first real recovery.
Start by listing every object-stored data type in your gitlab.rb configuration and in your storage buckets, then confirm which buckets hold which data and who can delete from them. Ask your provider and your GitLab administrators to confirm object versioning, retention rules, cross-region replication and lifecycle policies for each bucket, and check that backup or deletion credentials are scoped separately from day-to-day credentials. Recovery of a deleted artifact or image is only as good as the version history you can actually reach, so treat that check as the first deliverable.
Which object storage protection method fits each data type
This table compares four ways to protect GitLab data held in S3, GCS or Azure Blob Storage: provider versioning, cross-region or cross-account replication, a separate backup bucket with lifecycle rules, and Geo secondary sites. Read each row against the data type you hold (artifacts, LFS objects, uploads or registry blobs) to see what each method covers and what recovery work it leaves to you. Capabilities differ by provider and configuration, so confirm each one with your provider before you rely on it.
| Protection method | What it protects | What it does not protect and recovery effort | When it fits |
|---|---|---|---|
| Provider versioning | Overwritten or deleted artifacts, LFS objects, uploads and registry blobs in the same bucket, as long as versioning is enabled before the loss. | Does not protect against bucket deletion, account compromise or changes to the GitLab database that point to missing objects. Recovery means restoring object versions, which can be manual at scale. | Baseline for every object storage bucket holding GitLab data, combined with a second method. |
| Cross-region or cross-account replication | A copy of artifacts, LFS, uploads and registry blobs in another region or account, which survives a regional outage or a single-account incident. | Replicates deletions and corruption unless versioning and delete protection are also set. Does not provide a point-in-time view of the GitLab database. Recovery needs a matching database restore. | Organizations that need a copy outside the primary account or region and accept the cost of a second bucket. |
| Separate backup bucket with lifecycle rules | Periodic copies of object-stored data that are isolated from day-to-day write access, with retention set by lifecycle rules. | Copies are only as current as the sync schedule, so recent uploads can be missing. Lifecycle rules must be tested, because a misconfigured expiry removes data you still need. Recovery requires a planned copy-back. | Teams that want retention independent of GitLab and a copy that operators cannot easily delete. |
| Geo secondary site | A continuously synchronized secondary site in another region, which holds replicated object-stored data and can be promoted during a disaster. | Does not protect against logical corruption that replicates to the secondary. Geo still requires backups, and it adds a full second GitLab deployment to run and upgrade. | Instances that need a warm standby with a low data-loss window and can support a second GitLab environment. |
Provider settings to confirm on S3, GCS and Azure
Amazon S3 buckets used for GitLab object storage should be reviewed for object versioning, cross-region or cross-account replication, and whether Object Lock is in use. Versioning keeps prior object versions when GitLab overwrites or deletes an object, which matters when a bad deletion or a faulty job reaches the bucket. Replication copies objects to a bucket in another region or account, and it is worth checking whether replication covers existing objects, delete markers and encrypted objects in your setup. Confirm how lifecycle rules interact with versions, because an expiration rule can remove the copies you rely on.
- Object versioning: Confirm it is enabled on each bucket that holds artifacts, LFS, uploads or registry blobs.
- Replication: Check the destination region or account, the IAM role used for replication, and what replication does with deletes.
- Object Lock: Confirm whether a retention mode or legal hold applies and whether your organization requires it.
- GitLab permissions: The GitLab storage credentials need to write, read and list objects in the buckets it uses, without broader administrative rights.
- Lifecycle rules: Review expiration and transition rules so they do not delete versions or replicas earlier than your recovery plan expects.
Confirm these settings against current AWS documentation and the object storage section of your GitLab configuration.
Google Cloud Storage buckets used by GitLab should be checked for object versioning, soft delete, dual-region or multi-region placement, and retention policies. Object versioning preserves older generations of an object, and soft delete keeps removed data for a recovery window, but the names, defaults and behavior of these features change over time. Dual-region and multi-region buckets change where data is stored, while replication to a separate bucket is a different control, so confirm which of these your plan relies on. Retention policies can block deletion for a set period, which affects how GitLab cleans up old artifacts.
- Object versioning: Confirm whether it is on for each bucket that GitLab writes to, and how noncurrent versions are retained.
- Soft delete: Check the retention window and whether it applies to the buckets GitLab uses.
- Bucket location: Confirm whether dual-region or multi-region placement meets your residency and recovery requirements.
- Retention policy: Check whether a bucket-level policy or lock conflicts with GitLab's cleanup of artifacts and other objects.
- Service account permissions: The service account should hold object read, write and list permissions on the GitLab buckets only.
Confirm current behavior in Google Cloud documentation and in the object storage settings of your GitLab instance.
Azure Blob Storage containers used by GitLab should be checked for blob versioning, soft delete for blobs and containers, object replication, and immutability policies. Blob versioning and soft delete are separate features, and enabling one does not guarantee the other covers every case your recovery plan needs. Object replication between storage accounts is configured per pair of accounts and containers, so confirm which containers are included. Immutability policies can lock blobs for a period and may conflict with GitLab's own deletion behavior, which makes them a settings decision to test rather than assume.
- Blob versioning and soft delete: Confirm both settings on the storage account and whether they apply to the containers GitLab uses.
- Object replication: Check the paired account, the containers included in the policy and the replication status you monitor.
- Immutability policies: Confirm whether a time-based or legal hold policy is applied and how it affects GitLab cleanup.
- Access roles: The identity GitLab uses needs read, write and list access on the relevant containers, scoped to the storage account rather than the subscription where possible.
- Lifecycle management: Review rules that move or delete blobs so they match your recovery expectations.
Confirm these features against current Azure documentation and the object storage configuration in your GitLab instance.
How to set up versioning and replication for GitLab buckets
This sequence protects the GitLab data that the backup command skips when it lives in object storage. You inventory each bucket, turn on versioning, replicate to a separate region or account, set retention for old versions, confirm GitLab points at the right buckets, and prove recovery with a documented restore test.
Inventory buckets by GitLab data type
Start by listing every bucket and connection GitLab uses for artifacts, LFS objects, uploads, package and registry storage, and any other object-backed data in your deployment. Your platform team should pull these names from the object storage section of gitlab.rb or the Helm values, not from memory, and record which bucket holds which data type, the provider, and the owning account. Confirm the list against the storage settings in the instance itself. The step is done when every data type has exactly one named bucket in a written inventory that the backup owner has reviewed.
Enable versioning on each bucket
For each bucket in the inventory, turn on object versioning in the provider console, CLI or infrastructure-as-code. Versioning keeps prior copies when an object is overwritten or deleted, which is the protection the GitLab backup archive does not give you for objects stored outside the instance. Check the provider documentation for any state change that cannot be reversed, such as whether versioning can be suspended later, and record the setting in your change log. Verify by uploading a test object, overwriting it, and listing its versions. The step is done when each bucket shows more than one version for the test object.
Configure cross-region or cross-account replication
Create a destination bucket in a separate region or a separate account, with versioning enabled first because most providers require it on both ends. Then configure replication from each source bucket to its destination, and make sure the replication role or service identity has only the permissions it needs to read the source and write the destination. Decide whether deletions should replicate. Replicating deletes can propagate accidental loss, so many teams replicate only creates and updates. Confirm the outcome by checking replication status on a newly written test object. The step is done when the test object reaches the destination bucket with a replicated status.
Set lifecycle rules for noncurrent versions
Without a rule, noncurrent versions accumulate and storage costs rise without limit. Define lifecycle rules that expire noncurrent versions after a retention period your recovery requirements set, and that keep a separate, longer retention for the destination bucket if your policy requires it. Apply the rules to the data types in your inventory, since artifacts and registry blobs often have different retention needs from uploads. Confirm the rules by reviewing the applied policy in the provider console and by checking that the test object's older versions are still present inside the retention window. The step is done when the policy is documented and visible on every bucket.
Confirm GitLab points at the intended buckets
Check that each object storage setting in the GitLab configuration names the bucket from your inventory, and that no data type still writes to a default or legacy bucket. Review the connection settings for each type, then make a small change in the test project, such as uploading an artifact and an LFS object, and confirm the new objects appear in the correct source bucket. Record the GitLab version and the configuration file revision used for the check. The step is done when a test upload from each data type lands in its documented bucket and the configuration is committed to version control.
Document and run a restore test
Write a restore runbook that names the bucket, the object version to recover, the destination for the restored data, and the owner who approves the restore. Then run the procedure on a non-production copy: delete a test object in the source bucket, restore the previous version or the replicated copy, and verify that GitLab can read the object again. Also confirm that the gitlab-secrets.json file and the backup archive needed for the same restore are available to the same team. Keep the test record with the date, GitLab version and results. The step is done when a named person has completed the restore and the signed-off record is stored with the runbook.
How restores line up object storage copies with the backup archive
The backup archive and the object storage copies must describe the same point in time. GitLab's database records refer to artifacts, LFS objects, uploads and registry blobs by identifier, so if the archive is taken at one moment and the bucket is captured at another, a restore can bring back records that point to files the bucket no longer holds. Users then see broken downloads and failed pipelines that look like corruption but are really a timing gap. Treat the archive and the bucket state as one recovery point, and decide that point before the first backup run.
The safe order is to capture object storage state first, then take the archive, or to freeze writes for the window between the two. Object storage copies made earlier than the archive are fine because the archive's database rows will reference only objects that already existed. Objects written after the archive was taken are the risk: they are orphans that the restored database does not know about. Your restore runbook should say whether those orphans are kept, deleted or reconciled, and who makes that call. The exact restore steps are in the GitLab backup and restore documentation, so confirm them against the version you run.
Record the backup timestamp and the bucket state together in one place, such as a manifest stored alongside the archive. The manifest should hold the archive's backup ID, the GitLab version and edition, the bucket name, and the versioning or replication marker that identifies the bucket state. Without that pairing, an operator restoring at 3 a.m. has to guess which object versions match the archive. Test the pairing in a restore exercise, not only the archive, and confirm with your storage provider how to pin a bucket to a timestamp or version marker.
Protecting object storage backups from deletion and misuse
Object storage holds the artifacts, LFS objects, uploads and registry blobs that the GitLab backup command does not capture, so the bucket becomes a recovery asset in its own right. The cards below cover the ways that bucket can be lost, overwritten or misused, and the controls to weigh against each one.
Deletion through the GitLab application
GitLab writes and removes objects in the same buckets it reads from, so a bad cleanup job, a misconfigured retention setting or an administrator action can delete data that no GitLab backup archive contains. Keep the GitLab application credentials limited to what the instance needs, and confirm whether your object store can block deletes from that identity while still allowing writes. Test a deliberate deletion in a staging bucket before relying on the setup.
Credential compromise with delete rights
A leaked access key that can both write and delete turns a single secret into a destruction path for production data and its backups. Use separate credentials for the application and for backup or replication jobs, scope each to specific buckets and actions, and rotate them on a schedule. Ask your provider how key rotation, short-lived credentials and key expiry are handled for your account type.
Replicas inheriting bad writes
Cross-region or cross-account replication copies a corrupted or deleted object to the replica as faithfully as a valid one, so a replica alone is not a recovery point. Pair replication with object versioning on both sides so earlier versions remain retrievable. Confirm with your provider whether deletes propagate, whether delete markers replicate, and how replication handles versions that already existed before the rule was created.
Encryption key management for buckets
If buckets use customer-managed keys, losing or disabling that key makes every stored object unreadable, which is a recovery failure that looks like a storage outage. Decide who owns the key, where it is stored, how it is rotated and who can disable it, and keep the key policy out of the same identity that writes backups. Confirm the key deletion waiting period and recovery options with your provider before you enable them.
Cross-account access during incidents
During a breach or a disputed administrator action, responders may need read access to backup buckets held in a separate account or project, and granting that access ad hoc often weakens the controls in place. Document a break-glass role in advance, specify who may assume it and how long it lasts, and rehearse the grant and revocation steps. Confirm how your provider logs role assumption and whether cross-account policies can be time-bounded.
Access logging for backup buckets
Without object-level logs you cannot establish what was read, deleted or overwritten, or when, which matters both for incident scope and for evidence in later reviews. Enable server access or data event logging on backup buckets, send logs to a store the backup identities cannot modify, and set a retention period for them. Confirm with your provider which events are logged by default, what the logging cost model is, and whether log delivery is guaranteed.
What buyers ask about object storage backup work
What drives the cost of object storage backup work for GitLab?
Cost depends on the amount of work inside your buckets and the design around them. The main drivers are how many buckets hold GitLab data (artifacts, LFS objects, uploads and registry blobs), total data volume and how fast it grows, whether you need cross-region replication or a separate backup account, how retention and lifecycle rules are designed, and how much restore testing you want documented. Engineering effort is largely one-time for design and build, then recurs when you review retention or rehearse restores. Your cloud provider bills storage, replication and transfer directly and those charges recur. Ask for a written scope that lists each bucket, the replication target and the test plan, then compare the quote against that scope.
Can our own team back up GitLab object storage data without outside help?
Yes, a team with cloud storage and GitLab administration skills can do it. The GitLab backup and restore documentation explains what the backup command covers, and it does not cover the object storage content you have moved out of the instance. The harder part is usually design: deciding which buckets need protection, how long versions are kept, where copies live, who can delete them and how a restore is rehearsed. Teams without a dedicated cloud storage owner often find the restore test takes the longest. An outside partner can take on design and build under a written scope while your team keeps ownership of accounts and the runbook. Confirm internally who will run restores before you decide.
How long does setting up object storage backups usually take?
Setup time depends on how many buckets and environments are involved, how much replication you need and how many restore tests must be documented. A single instance with one storage provider and existing versioning is far less work than a multi-account design with cross-region copies and approval steps. Other drivers include access approval lead times from your cloud team, change windows on production GitLab and how long the first full restore takes once real data is in scope. No duration is given here because it depends on your environment. Ask for a timeline in the written scope, list the dependencies that could move it as assumptions, and check it against your change calendar.
What does the work need from our team before it starts?
The work needs a short list of access, decisions and time from your side:
- Cloud account access with a role limited to the GitLab buckets and any backup accounts involved
- Bucket owners who can confirm which data lives where and approve lifecycle and retention rules
- Named security approvers for changes to encryption, access policies and deletion controls
- A restore window agreed with the business, during which test restores can run on an isolated instance without touching production
- The current GitLab version and object storage settings from your configuration file
Without a restore window, the backup design cannot be proven. Agree these items in the written scope so that approvals do not become the critical path.
Is bucket versioning alone enough to protect GitLab object storage data?
Versioning helps, but it does not provide a restorable backup of GitLab by itself. It keeps earlier object versions for as long as your retention rules allow, which protects against some overwrites and deletions. It does not capture the database references to those objects at the same point in time, so object state and the database backup must be aligned during restore. A user or compromised credential with delete permission may also remove versions if the policy allows it. Replicating data to a separate account or region adds separation, and regular restore tests show whether the combination works. Treat versioning as one control in the design rather than the whole plan.
How does object storage backup differ from GitLab Geo for recovery?
Object storage backups give you point-in-time copies that you restore on demand. GitLab Geo provides a warm-standby secondary site in another region that is continuously synchronized, and it is available in the Premium and higher tiers. Geo reduces the time to a working secondary, but it copies changes as they happen, so an accidental deletion or corrupted change can replicate too. Backups let you go back to an earlier state. Many organizations use both: Geo for fast failover and backups for point-in-time recovery. Your choice depends on your recovery time and data loss tolerance, which should be set before design starts.
What should a restore test for object storage data actually check?
A useful test restores the database, the Git data and the object storage content together on an isolated instance running the same GitLab version and edition as the backup. It then checks that job artifacts download, LFS objects resolve, uploads display in issues and merge requests, and container registry images can be pulled. Record how long each step takes, which objects fail and what was done to fix them. Repeat the test after major configuration changes and after retention rules change. Keep the results as evidence for your change and continuity records. Ask any partner to show the test plan and the evidence it will produce before work begins.
What to confirm before relying on bucket-level protection
Bucket versioning and replication can protect GitLab data that the backup command does not capture, but only if they match how your instance writes and deletes objects. The items below are the checks to close with your GitLab version, your storage provider and your own compliance and restore team before you treat bucket protection as a recovery path.
Data types in each bucket
Confirm which GitLab data types land in each object storage bucket for your exact version and configuration. Artifacts, LFS objects, uploads, packages and container registry blobs may each use a separate bucket or prefix, and some may stay on local disk. Map every bucket to a data type, then check that none is missing from both the backup archive and your bucket protection plan. Ask GitLab support to confirm the mapping for your release.
Version and SDK behavior
Confirm that your GitLab version and the object storage client it uses behave as the documentation describes for versioning, lifecycle rules and object overwrites. Provider SDK and storage driver changes can alter how multipart uploads, checksums or delete markers are handled. Test with a representative object set after every GitLab upgrade and after any change to the storage configuration in gitlab.rb.
Retention and GitLab deletion
Check how retention settings interact with GitLab deletions. When GitLab removes an artifact, upload or registry blob, the provider may keep prior versions for the retention period, which can keep deleted content recoverable for longer than expected. Decide whether that is acceptable for your data handling rules, and confirm who can shorten or purge retained versions. Ask the provider to explain how lifecycle rules apply to noncurrent versions.
Replication target and residency
Confirm that a cross-region or cross-account replication target meets your data residency and sovereignty rules. Replicated objects can leave the jurisdiction or account boundary of the primary bucket, and replication may copy encryption keys, tags or metadata that carry sensitive information. Verify the target region, the key management setup at the destination and who holds administrative access there with your legal and privacy owners.
Cost of retained versions
Estimate the storage cost of old versions and replicas before you rely on them. Version history on large artifact, registry and LFS buckets grows with every overwrite and deletion, and replication doubles the stored bytes at the target. Ask your storage provider for pricing on versioned and replicated objects in your regions, and set lifecycle rules that match the retention you have decided. Recurring storage charges usually outweigh the one-time setup effort.
Tested restore procedure
Confirm that a restore has been run end to end, not just described. Practice recovering a bucket object version, a replicated copy and the matching database and secrets state into a separate environment, and time the steps your team would follow during an incident. A restore that works on a few test objects may fail on version mismatches or missing secrets, so record the results and repeat the test after each major change.
How DeployOpen works on this
DeployOpen scopes object storage backup work for GitLab in writing before any build starts, then configures provider versioning, replication and restore checks in your own cloud accounts or data centers. Engagements are fixed-scope, with deliverables and timelines agreed up front, and ongoing support is available as a retainer.
How we scope
- Inventory which GitLab data types live in your object storage buckets and which stay on local disk.
- Record the bucket provider, regions, versioning state and any existing replication rules for each store.
- Agree in writing which restore targets, retention periods and recovery tests the engagement covers.
How we build
- Configure bucket versioning, lifecycle rules and cross-region replication for artifacts, LFS, uploads and registry blobs.
- Align object storage settings with the GitLab backup configuration so the two recovery paths match.
- Test restores into an isolated environment and record the results against the agreed recovery targets.
How we hand over
- Deliver a runbook covering each bucket, its protection settings, owners and the restore sequence.
- Document access controls on backup buckets, including who can delete versions and how keys are rotated.
- Walk your operations team through a restore drill so they can run the procedure without us.
How we support
- Offer ongoing support and scheduled restore tests as a retainer after handover.
- Review bucket settings and replication health when GitLab versions or storage providers change.
- Confirm with your storage provider which versioning and replication features apply to your account tier.

