Originally published at nazmulkhalid.com.
TL;DR: When your software runs on servers you can't SSH into, an update has to be safe without anyone watching. In Bicitra Commerce, a self-hosted eCommerce platform, the update pipeline checks every planned file write (permissions, ownership, disk space) before touching anything, verifies each release against a checksum, skips vendor/ when dependencies haven't changed, and rolls back completely on any failure.
Bicitra Commerce ships differently from most SaaS products. There isn't one application that I deploy and control. Each merchant runs their own Laravel/MySQL install, usually on the cheapest shared hosting they can find. That gives merchants independence and low hosting bills. It also means every update lands on a production server I have no SSH access to, under hosting conditions I can't predict.
The failure that shaped the design
The pipeline checks permissions first because a real update failed halfway through on a permissions problem. On one host, vendor/ had been left owned by the SSH user after a manual composer install.
The update only found out once it tried to write files it wasn't allowed to touch. By then the site was already down and the update was half-applied. There's no ops team on the other end of a shared hosting account to step in.
That's the worst moment to discover a permissions problem.
Check everything before anything goes down
The fix is to check what the update will need before changing a single file. The pipeline now checks every planned file write (permissions, ownership, disk space) and reports a problem with zero files touched.
Before, the same problem showed up three steps into an update with the site offline. Now a cPanel host that disables symlink() outright, or a server with mixed file ownership after a manual install, gets caught before it turns into an outage.
One check isn't enough
The other safeguards come from the same constraint: nobody can step in if an update breaks halfway.
- Checksum verification. Every release package is checked against a checksum before it's applied, so a corrupted or incomplete download can't get partly installed.
-
Skip what hasn't changed.
vendor/is left alone when dependencies haven't changed. That means fewer files written and fewer things that can fail on hosts with slow or unreliable disks. - Roll back completely. If an update fails partway, it rolls back, and that includes removing new top-level files the failed update had already added, not just restoring the ones it changed. A failed update should leave the site exactly as it was.
What I'd tell anyone shipping to other people's servers
Build for the update that fails on a host you'll never see. Most update tooling assumes a person is watching and can react when something breaks.
Once you ship to infrastructure you don't run (shared hosting, a customer's VPS, an air-gapped network), that person is gone. A broken update isn't a Slack alert anymore. It's a merchant's store offline with nobody who can log in and fix it.
There's no single clever check that solves this. It's four habits together: check before you commit, verify before you apply, change as little as you can, and make failure fully reversible.
I'm Nazmul Khalid, a lead software engineer. I build SaaS products and MVPs for founders. The Bicitra Commerce case study is here, and how I work is on my hire page.
Top comments (0)