Removing content for good
Deleting a page or an image in the editor and publishing that change takes it off the website. It does not make it disappear. A cratly website is a Git repository, and every earlier version of every file stays in its history: anyone with access to the repository can still open the photo as it was last month.
Usually that is what you want. Sometimes it is not:
- a person shown in a photo asks for it to be erased,
- a page named someone who has since withdrawn their consent,
- a password, key or token was saved into a file by mistake.
This page describes how to remove such content from every place a cratly website keeps it, and in which order.
Irreversible, and it touches everyone working on the site
Removing something from the history rewrites that history. Every draft and every copy of the repository made before the rewrite becomes outdated. Plan a moment when no one is editing, and tell your team beforehand.
0. A credential? Replace it first
If what leaked is a password, a key or a token, revoke it and issue a new one before anything else. Whoever copied it already has it, and rewriting history will not take it back. The steps below then only clean up after a credential that no longer works.
1. Take it off the website
Delete the image or edit the page in the editor, and publish the draft as usual. Look for every place the content was used: the same photo can appear on several pages, as a teaser image, or in a container. Once the pipeline has deployed, the website no longer shows it, and that should not wait for the rest of this page.
2. Find every copy
The repository is not the only place. For a site using the cratly pipeline templates, these also hold the content:
| Where | What is in it | Cleaned up in |
|---|---|---|
| The repository's history | Every version of the file, on every branch and tag | Step 4 |
| Drafts | A draft is a branch; an open one may still contain the file | Step 3 |
| Merge requests | Published and discarded drafts keep their changes on display, images included | Step 5 |
| Pipelines | Every build stores the finished site, image variants included, as an artifact | Step 5 |
| The web server | deploy-rsync-ssh with strategy: release-symlink keeps past releases; storage may keep versions or backups | Step 6 |
| Browsers of the editing team | A preview caches the built site in the browser | Step 6 |
| Local copies and forks | Clones made by developers, forks of the project | Step 6 |
| Search engines and archives | Cached copies of the page and the image | Step 6 |
| Backups | Backups of GitLab and of the web server | Step 6 |
3. Prepare
- Publish or discard every open draft. A draft that is still open after the rewrite rests on the old history. Merging it would bring the removed file back.
- Make sure no one is editing. A save during the rewrite either fails or brings the old history back.
- Check your role. Most of the steps below need the GitLab Owner role of the project. A chief editor (Maintainer) is not enough.
- Export the project as a backup (Settings → General → Advanced → Export project). Keep it only until the work is done, then delete it: it contains what you are removing.
4. Rewrite the history
GitLab offers two ways. Both are available on every tier, including the free one.
In GitLab's interface: remove blobs
This suits images and other media files best. Git stores each version of a file as a blob, and GitLab can delete blobs by their ID, wherever they occur, even when the file was moved or renamed in the meantime. It needs GitLab 17.3 or newer.
In a current copy of the repository, list the blob IDs of every version of the file. For a file at
media/team/jane-doe.jpg:shgit fetch --all git rev-list --objects --all | grep 'jane-doe.jpg'Each line starts with a blob ID. If the photo was cropped or edited, there is one ID per version. Look for other names too: an earlier upload with a different name, a copy in another folder.
In GitLab, go to Settings → Repository → Repository maintenance → Remove blobs, enter the IDs one per line and confirm by typing the project's path.
Wait for the confirmation email, or at least five minutes.
Go to Settings → General → Advanced, click Run housekeeping, wait at least 30 minutes, then click Prune unreachable objects.
Text inside a page, such as a name or a token, can be removed the same way using Redact text in the same section. It replaces exact matches with ***REMOVED*** in every version of every file. It does not accept patterns.
With git filter-repo
This is for when the GitLab version is older, or when whole folders or many files have to go. It needs git-filter-repo installed locally.
git clone --bare --mirror https://gitlab.example.com/<group>/<site>.git
cd <site>.git
# every version of a file, under each name it ever had
git filter-repo --path media/team/jane-doe.jpg --path media/jane.jpg --invert-paths
# or: text replaced in every version of every file
git filter-repo --replace-text <(echo 'Jane Doe==>***REMOVED***')
cp filter-repo/commit-map ../commit-map
git config --unset remote.origin.mirror
git push origin --force 'refs/heads/*'
git push origin --force 'refs/tags/*'The push is refused for the published branch while it is protected. Allow force pushes for the moment in Settings → Repository → Protected branches, and restore the protection as described in Roles right afterwards.
Wait at least 30 minutes, then go to Settings → Repository → Repository maintenance, upload commit-map and click Start cleanup. Only then does GitLab also drop the internal references it keeps for merge requests and pipelines. Without that step, the old versions remain reachable through them.
5. Clean up what GitLab keeps besides the history
GitLab keeps some information about commits in its database, and it stays visible after the history is rewritten:
- Merge requests. Open each merge request that added or changed the content and delete it (Edit → Delete, Owner role). With the editor, every published draft was a merge request, so look through the merged and closed ones as well.
- Pipelines and their artifacts. Each build stored the finished site, including the image and the variants generated from it. Artifacts of the cratly build template expire after three days, but GitLab keeps the artifact of the latest successful pipeline per branch regardless. Delete the pipelines that built the content (Build → Pipelines, open a pipeline, Delete, Owner role). Deleting a pipeline also deletes its artifacts.
6. Clean up outside GitLab
- Web server.
deploy-s3anddeploy-rsync-sshwithstrategy: in-placedelete files removed from the site with the next deploy.strategy: release-symlinkkeeps the lastkeep_releasesreleases for rollback, and each of them still contains the file: delete them underreleases/on the server. Object storage with versioning enabled keeps earlier versions of every object; delete those too. - Editors' browsers. A preview is cached in the browser of whoever opened it. Ask everyone who previewed the content to clear the stored data of the preview origin (on the hosted editor,
preview-editor.cratly.io) in their browser's site settings. - Local copies. Everyone who has a clone of the repository deletes it and clones again. An old clone pushed back by mistake brings the content back.
- Forks. GitLab cannot rewrite the history of a fork, and objects a project shares with its forks cannot be removed. Forks have to be cleaned up or deleted by whoever owns them.
- Search engines and archives. Request removal of the outdated page and image, for example with Google's Remove outdated content tool, and at web archives that have copied the page.
- Backups. Backups of GitLab and of the web server still contain the content until they expire under your retention rules. Whether that is acceptable, and for how long, is a question for your data protection policy rather than for this guide.
7. Check the result
Clone the repository again and search for every ID and name from step 4:
shgit rev-list --objects --all | grep 'jane-doe' git log --all -p -S 'Jane Doe'No output means the content is not reachable in the history any more.
Open the image's former address on the website. It answers with not found.
Check that the published branch is protected again, and that the repository check in the editor finds nothing.
When the removal was requested by a person, note what you removed and when. That record is what you can show them afterwards, and it does not have to contain the content itself.