How to Set Up a Deployment Pipeline for Your Website

How to Set Up a Deployment Pipeline for Your Website

A website deployment pipeline is the automated path your code takes from a Git repository to the server that serves it. You set one up by making your hosting environment pull from a branch, run any build steps, and swap the new files into place without dropping a single request. The core mechanic is simple: you push to Git, a webhook fires on the server, and a script on the server does the rest.

Why a Pipeline Matters for Website Deployment Pipeline Setup

Manually copying files over FTP or SSH is how sites break. You forget a file, you overwrite a config, or you upload while a user is submitting a form. A pipeline removes that human error by making the process deterministic. It also gives you a rollback story: if the new release is bad, you redeploy the previous commit. For a small site, the pipeline can be a single shell script. For a larger one, it can be a chain of stages, but the principle stays the same. The target search phrase for this guide is website deployment pipeline setup, and the approach below is provider agnostic, so it works whether your host gives you a bare metal box or a container platform.

The Two Protocols You Need to Know: Git and SSH

Your pipeline will use Git for version control and SSH for remote execution. Git pushes commits to a remote repository, and your server needs to fetch from that repository. SSH is how you run commands on the server without a password prompt, using a key pair. The first step is to generate a deploy key on your local machine and add the public half to your hosting account. Then you can test the connection from your terminal.

ssh -T [email protected]
# Expected output: Welcome to your server, deploy user.

That command confirms your key works. The user deploy is a dedicated account with limited permissions, not your root login. You want that separation because the webhook will trigger commands as this user, and you do not want a compromised webhook to give an attacker root access.

Structure Your Repository for Deployment

Your repository should have a clear separation between source and build output. If you use a static site generator, a bundler like Webpack, or a server-side framework that compiles assets, you need a build step. The pipeline must run that build on the server, not on your laptop, because the server environment is what matters. A typical layout has a src/ directory for your work and a public/ or dist/ directory that the web server points to. Your deploy script will rebuild that output directory from scratch each time, which avoids stale files.

For a dynamic site, the same idea applies, but you skip the static output and instead run migrations or cache clears after pulling the code. The key is to have a single entry point, usually a file named deploy.sh in the repository root, that the server executes. That script is version controlled, so everyone on the team can see what a deployment does.

Write the Deploy Script That Runs on the Server

The script needs to do three things in order: fetch the latest code, install any dependencies, and then atomically switch the live directory to the new code. The atomic switch is what gives you zero downtime. You do not copy files over the live directory. Instead, you build into a fresh release directory and then change a symlink.

#!/bin/bash
set -e
RELEASE_DIR="/var/www/releases/$(date +%s)"
LIVE_DIR="/var/www/live"
git clone --depth 1 --branch main /var/www/repo.git "$RELEASE_DIR"
cd "$RELEASE_DIR"
npm ci --production
ln -sfn "$RELEASE_DIR" "$LIVE_DIR"
sudo systemctl reload nginx

That script does a shallow clone from a bare repository on the same server, installs production dependencies, and then flips the symlink. The ln -sfn command is atomic: the old symlink is replaced in one step. A request that arrives before the flip goes to the old release; a request after goes to the new one. No request sees a half-written file. The reload of the web server picks up any new file handles, but it does not drop connections.

Set Up the Webhook to Trigger the Script

Your Git host can send a POST request to an endpoint on your server whenever you push. You do not want that endpoint to be a general command runner, because that is a security hole. Instead, you run a small listener that checks the payload for a secret token and then executes your deploy script. A common way is to use a systemd service that runs a tiny HTTP server on a local port, or you can use a CGI script if your host supports it. The important part is the header check.

POST /deploy HTTP/1.1
Host: deploy.example.com
X-Deploy-Token: your-secret-token
Content-Type: application/json

Your listener validates that header before it runs anything. If the token is wrong, it returns a 403 response and logs the attempt. If it is correct, it runs bash /var/www/repo/deploy.sh in the background and immediately returns a 202 Accepted so the Git host does not time out. You can test the webhook manually with curl from your local machine before you connect it to your Git host.

Handle Rollbacks and Failed Deployments

No deployment is perfect, so you need a way to go back. Because your releases are timestamped directories, you can keep the last five releases and delete older ones. To roll back, you just point the symlink at a previous release directory. The command is simple, but you want it in a script too, so you do not mistype a path under pressure.

ln -sfn /var/www/releases/1710000000 /var/www/live
sudo systemctl reload nginx

Before you flip the symlink, your deploy script should run a health check. For a static site, that might be a check that the main HTML file exists and is nonempty. For a dynamic site, it could be a curl request to a health endpoint that returns a 200 status. If the check fails, the script exits and leaves the old symlink in place. That way a bad build never goes live.

Secure the Pipeline and Monitor It

Your deploy user should only have write access to the releases directory and read access to the repository. The webhook listener should run as a separate user with no shell access. Use SSH keys with passphrases only if you are using an agent, and never store private keys in the repository. For monitoring, have the deploy script write a log line to a file with the commit hash, the timestamp, and the result. You can then check that log with tail -f /var/log/deploy.log during a deployment to see progress in real time. Set up a simple cron job to ping a health check service if the last deploy was more than a day ago, so you know if the webhook silently stopped firing.

Next Steps for Your Own Setup

Start small. Pick one low-traffic site or a staging environment, write the deploy script, and test the rollback path before you test the happy path. Then connect the webhook and push a trivial change to confirm the whole chain works. Once you trust it, apply the same pattern to your production site, but keep the first few deployments manual so you can watch the logs. Automate only what you understand, and you will have a pipeline that survives a bad commit, a server restart, and a change of hosting provider without a rewrite.

Related articles

Subscribe to our newsletter

Get the latest hosting tips, performance insights, and industry news.