Continuous deployment does not always require complex tools or managed platforms. For small to medium projects, a lightweight deployment pipeline based on GitHub, SSH, and a simple synchronization script can be reliable, transparent, and easy to maintain. This approach gives you full control over your infrastructure while avoiding unnecessary complexity.

In this article, we will walk through setting up a deploy pipeline where a Linux server securely connects to a GitHub repository using SSH and automatically pulls updates every five minutes. This pattern is commonly used for personal projects, internal tools, and early-stage production systems.

Overview of the Deployment Flow

The deployment flow is intentionally simple. Your application code lives in a GitHub repository. A remote Linux server has SSH access to that repository and periodically checks for updates. When changes are detected, the server pulls the latest code and runs a deployment script.

This setup avoids exposing credentials in plain text and does not rely on GitHub Actions or external CI services. Instead, it uses SSH keys for authentication and cron for scheduling.

Generating an SSH Key on Linux

The first step is to generate an SSH key pair on the server that will deploy the application. SSH keys provide a secure and passwordless way to authenticate with GitHub.

ssh-keygen -t ed25519 -C "deploy@yourserver"

When prompted, choose a secure location (the default is usually fine) and optionally set a passphrase. This command creates a private key and a public key. The public key will be added to GitHub, while the private key stays on the server.

Display the public key using:

cat ~/.ssh/id_ed25519.pub

Adding the SSH Key to GitHub

Copy the public key and add it to GitHub under Settings → SSH and GPG keys. This allows the server to access private repositories without entering a username or password.

To verify the connection, test the SSH authentication:

ssh -T [email protected]

A successful message confirms that the server can securely communicate with GitHub.

Cloning the Repository on the Server

Once SSH access is configured, clone your repository using the SSH URL. This ensures all future interactions use key-based authentication.

git clone [email protected]:your-org/your-repo.git

Place the repository in a predictable directory such as /var/www/app or /opt/app. Consistent paths make automation scripts easier to manage and debug.

Creating a Deployment Script

The deployment script is responsible for pulling the latest code and running any required build or restart steps. This script can be as simple or as advanced as your project requires.

#!/bin/bash

set -e
cd /var/www/app

git fetch origin
git reset --hard origin/main

npm install
npm run build
systemctl restart app.service

Make the script executable:

chmod +x deploy.sh

Using set -e ensures the script stops immediately if any command fails, preventing partial or inconsistent deployments.

Scheduling a Sync Every Five Minutes

To automate deployments, use cron to run the script at a fixed interval. This approach checks for updates every five minutes and applies them if needed.

crontab -e

Add the following line:

*/5 * * * * /var/www/app/deploy.sh >> /var/log/deploy.log 2>&1

This configuration runs the deployment script every five minutes and logs all output for troubleshooting. Logging is essential when deployments are automated and unattended.

Security and Best Practices

While this pipeline is simple, security should not be overlooked. Limit SSH key access to read-only repositories when possible, and restrict server permissions so only the deployment user can modify application files.

It is also good practice to pin deployments to a specific branch and avoid running untrusted scripts. For sensitive systems, consider combining this approach with signed commits or protected branches on GitHub.

"A simple deployment pipeline is often more reliable than a complex one that nobody fully understands."

Conclusion

A deployment pipeline built with GitHub, SSH, and a scheduled sync script offers a pragmatic balance between automation and control. It removes manual deployment steps while remaining easy to debug and extend.

For many projects, especially in their early stages, this approach is more than sufficient. As requirements grow, the same foundations can later be integrated into more advanced CI/CD systems without wasted effort.