Featured image for How I deploy without Git on the server

How I deploy without Git on the server

Explainer

For a while I had Git installed right on the live server and pulled changes straight down onto it. And it worked. It also put a load of overhead on a machine that should really be doing one job, which is serving the site. So I stopped, and honestly the whole thing got cleaner for it.

Here's how I do it now, and why I bother with the extra steps.

What CI/CD actually is

If you've never run into CI/CD, Jenkins, DeployHQ and the rest, they're all just ways of getting your changes onto a live site with as little disruption as possible. Instead of you dragging files up over FTP by hand (and praying your connection holds), the work happens between the servers, and your laptop stays out of it. A runner, if you hear the term, is basically a robot butler: a machine that sits there waiting to build and ship your code when you tell it to.

None of that stops you writing a bug, mind. It just makes sure the files that land are the files you meant to send, and that they all actually arrive.

I've had good results with DeployHQ over the years, but it's a paid service. GitLab has a tidy version built in too. But you don't strictly need any of it to deploy safely, and that's really what this post is about.

How the deploy actually works

The steps are the same whatever the stack is.

  1. Pull the latest from the repo onto a build machine (not the live server).
  2. Build the project there.
  3. Send the finished build up to the production server over SFTP, rsync, or whatever transfer method suits.

The important bit is where it lands: a new versioned folder of its own, sitting alongside the live one rather than on top of it. So v-1_0_1 sits quietly next to the current live v-1_0_0.

Once the new version's up there and I've checked it over, I rename the current live directory out of the way and rename the new one into its place. Then I tidy up the build artefacts (the leftover files the build process spits out), but I leave the old version sitting where it is. I know that one worked.

Why keep the old version?

Because if the new build turns out to be broken despite all the testing (it happens), I roll back in seconds by renaming the folders back. That's the whole reason for the dance. A straight overwrite is quicker, right up until the moment something breaks and you've got nothing to go back to.

A word on git fetch and git pull

Get in the habit of running git fetch before git pull. Even on a solo project, odd bits appear in the repo you weren't expecting. fetch just goes and looks at the state of things without changing your local copy; pull is fetch plus actually merging those changes in. Fetching first, every time you sit down to work, means you always know where the repo stands before you touch anything.

And if you find yourself reaching for --force to make a push or pull behave, stop. That's usually a sign something's genuinely wrong with the repo. Sort out the underlying problem. Forcing it just buries the mess for later.

How I keep branches straight

I run three permanent branches: local, staging and main.

Local is where I actually work. It never gets committed as it stands; I pull things in, do the work, and merge back out when it's ready.

Staging is production-ready code with the debugging still switched on, deployed to a staging server (a server set up to mirror the live one as closely as I can manage). It lets me test in something close to the real environment without going anywhere near live data or risking downtime.

Main is the clean one. Bug-free, and the only branch a production server ever gets built from.

Past those three, I branch off freely for individual jobs. Frontend scaffolding on one branch, API work on another, each one kept to its own task. When a job's done, it merges back in. That matters most on projects with more than one developer: you really don't want someone else's half-finished work landing in your branch while you're mid-task. So the integration happens at the end, deliberately, rather than getting forced halfway through something else.

There are more thorough setups than this, with more safety nets built in, and on bigger jobs they earn their keep. But for a lot of what I do, keeping a close eye on things and knowing what should happen covers it. After all, it's worth a bit of effort up front to make deploying boring, instead of doing it by hand and holding your breath every time.

deployment · Git · CI/CD · rollback · branching · staging · SFTP · DevOps