EpicMicrodev
Back to Blog
GUIDE · Linux & Servers

Deploy a Private GitHub Repository to an Ubuntu VPS

Epic Microdev9 min read

Learn how to connect an Ubuntu VPS to a private GitHub repository using SSH deploy keys, clone the project into /opt, and prepare it for Docker Compose deployment.

1. Why Private Repository Deployment Needs Care

A private GitHub repository is usually the source of truth for your production application. When a VPS needs to pull that code, the server needs a secure way to authenticate without depending on a developer's personal laptop.

The clean approach for a single repository is usually a GitHub deploy key. A deploy key is an SSH key attached directly to one repository. The public key is added to GitHub, and the private key stays on the server.

GitHub deploy keys are read-only by default. For normal production deployments, keep them read-only so the VPS can pull code but cannot push changes back to the repository.

This guide assumes you already have an Ubuntu VPS with Docker installed and a deploy user prepared from the earlier articles in this series.

2. Confirm the Deployment User

Log in as the deploy user you created in the Docker setup guide:

bash
ssh deploy@YOUR_VPS_IP

Confirm the current user:

bash
whoami

Expected output:

text
deploy

Also confirm that Docker commands work for this user:

bash
docker ps
docker compose version

Do not clone production code as root. Keep application files owned by the deploy user so CI/CD and manual maintenance behave predictably.

3. Choose the Production Directory

Production projects should live in a predictable server location. A common pattern is to keep application repositories under /opt.

For this guide, we will use:

text
/opt/myapp

If the directory does not exist yet, create it from an administrator account:

bash
sudo mkdir -p /opt/myapp
sudo chown -R deploy:deploy /opt/myapp

Then return to the deploy user and check ownership:

bash
ls -ld /opt/myapp

The directory should be owned by deploy.

4. Generate a GitHub Deploy Key on the VPS

While logged in as deploy, create a dedicated SSH key for this one repository:

bash
ssh-keygen -t ed25519 -C "myapp-vps-deploy-key" -f ~/.ssh/myapp_deploy_key

When asked for a passphrase, you can leave it empty for automated server pulls. Protect the server account carefully because this private key can read the repository.

This creates two files:

text
~/.ssh/myapp_deploy_key
~/.ssh/myapp_deploy_key.pub

Never copy the private key into GitHub, a chat message, documentation, or a public repository. Only the .pub file belongs in GitHub.

Set strict permissions:

bash
chmod 700 ~/.ssh
chmod 600 ~/.ssh/myapp_deploy_key
chmod 644 ~/.ssh/myapp_deploy_key.pub

5. Add the Public Key to GitHub

Print the public key from the VPS:

bash
cat ~/.ssh/myapp_deploy_key.pub

Copy the full output. In GitHub, open the repository, then go to Settings, Deploy keys, Add deploy key.

Use a clear title, for example:

text
Production VPS - myapp

Paste the public key into the Key field and leave write access disabled unless your deployment process intentionally needs to push to the repository.

A deploy key can only be used with one repository. If one server needs access to multiple private repositories, create a separate key for each repository.

6. Configure SSH to Use the Deploy Key

Create or edit the deploy user's SSH config:

bash
nano ~/.ssh/config

Add this configuration:

text
Host github.com-myapp
    HostName github.com
    User git
    IdentityFile ~/.ssh/myapp_deploy_key
    IdentitiesOnly yes

Protect the config file:

bash
chmod 600 ~/.ssh/config

The alias github.com-myapp lets this repository use the dedicated deployment key without changing global SSH behavior.

7. Test GitHub SSH Access

Test the SSH connection using the alias:

bash
ssh -T git@github.com-myapp

On first connection, SSH may ask you to trust GitHub's host key. Confirm the fingerprint against GitHub's published SSH key fingerprints before accepting.

A successful test usually says that authentication worked and that GitHub does not provide shell access.

GitHub's SSH test command may return a non-zero shell exit code even when authentication succeeds. Read the message, not only the exit code.

If you see Permission denied (publickey), check that the public key was added to the correct repository and that the SSH config points to the matching private key.

8. Clone the Private Repository into /opt

Move to the parent directory and clone the private repository. Replace OWNER and REPO with your real GitHub owner and repository name.

bash
cd /opt
git clone git@github.com-myapp:OWNER/REPO.git myapp

Then enter the project directory:

bash
cd /opt/myapp

Confirm the remote URL uses the SSH alias:

bash
git remote -v

You should see a remote similar to:

text
origin  git@github.com-myapp:OWNER/REPO.git (fetch)
origin  git@github.com-myapp:OWNER/REPO.git (push)

9. Set Ownership and File Permissions

If the repository was cloned as deploy into a directory owned by deploy, permissions may already be correct. It is still worth checking:

bash
pwd
whoami
ls -la

From an administrator account, you can repair ownership if needed:

bash
sudo chown -R deploy:deploy /opt/myapp

Avoid broad permissions such as chmod 777. Production deployment problems should be fixed through ownership and clear service boundaries, not by making every file writable by everyone.

10. Create the Production Environment File

Most Docker Compose applications need environment variables. Keep real production secrets on the server, not committed to Git.

If the project includes an example file, copy it:

bash
cp .env.example .env
nano .env

Fill in production values for domains, API keys, database passwords and service secrets.

Do not commit .env files containing real secrets. The repository should include .env.example for structure, while the production .env stays on the VPS.

Restrict the environment file:

bash
chmod 600 .env

11. Verify Docker Compose Files

Before starting anything, confirm that Docker Compose can read the project configuration:

bash
docker compose config

This expands and validates the Compose file. It is useful for catching indentation errors, missing environment variables and invalid service definitions.

If the project uses Makefile commands, inspect the available deployment targets:

bash
make help

For a Docker-first project, the deploy user should be able to run the production build/start commands without installing application dependencies directly on the host.

12. Pull Updates Safely

Once the repository is cloned, future updates should be predictable and fast-forward only unless you deliberately choose another release strategy.

bash
cd /opt/myapp
git fetch origin main
git checkout main
git pull --ff-only origin main

The --ff-only flag prevents Git from creating merge commits during production pulls. If the server branch has diverged, stop and fix the deployment state intentionally.

A simple manual deployment after pulling might look like:

bash
docker compose up -d --build
docker compose ps

Later, CI/CD can run the same small set of commands over SSH.

13. Troubleshooting GitHub Access

If cloning fails with Permission denied (publickey), start with these checks:

  • the public key was added to the correct GitHub repository
  • the deploy key is enabled in repository settings
  • the SSH config points to the matching private key
  • the remote URL uses the alias github.com-myapp
  • the private key file is readable only by the deploy user
bash
ssh -vT git@github.com-myapp

The verbose SSH output shows which identity file SSH is trying. That is often the fastest way to spot a wrong key path or alias.

If port 22 is blocked by a network provider, GitHub also documents SSH access over port 443 through ssh.github.com. Use that only when normal SSH connectivity is unavailable.

14. Final Checklist

deploy user can log in with SSH
/opt/myapp exists
/opt/myapp is owned by deploy
dedicated repository deploy key generated
public deploy key added to GitHub
deploy key is read-only unless write access is deliberately required
SSH config alias created
GitHub SSH authentication tested
private repository cloned into /opt
.env created on the VPS
.env is not committed to Git
docker compose config succeeds
production pulls use git pull --ff-only

At this point, the VPS can securely pull your private repository and is ready for the next deployment step: running the production Docker Compose stack behind a reverse proxy with HTTPS.

Continue the Ubuntu VPS Series

Previous Article

Install Docker & Docker Compose on Ubuntu for Production

Read article

Next Article

Caddy on Ubuntu: Reverse Proxy, Domain & Automatic HTTPS

Coming Soon

Also Coming

GitHub Actions CI/CD for Ubuntu VPS Deployments

Coming Soon