Git과 GitHub 차이점: VPS 운영자를 위한 핵심 가이드
Git은 로컬 환경의 버전 관리 도구이며 GitHub는 이를 호스팅하는 서비스입니다. VPS 서버 운영 시 Git을 활용한 배포 전략과 두 개념의 명확한 차이를 설명합니다. 서버 관리자가 알아야 할 저장소 관리 기초를 확인하십시오.
GitHub란 무엇인가?
GitHub는 Git 저장소를 호스팅하고 그 주위에 웹사이트 기능을 구축해 주는 서비스입니다. Git은 사용자의 컴퓨터나 서버에서 실행되는 버전 관리 프로그램입니다. GitHub는 Git을 기반으로 한 특정 기업의 제품이며, 2018년부터 Microsoft가 소유하고 있습니다. Git을 매일 사용하면서도 GitHub를 전혀 열지 않을 수 있습니다. 하지만 Git 없이는 GitHub를 사용할 수 없습니다.
이 구분은 VPS(가상 사설 서버)를 운영하는 순간 중요해집니다. Git은 설정 파일과 배포 스크립트의 이력을 기록하는 도구입니다. GitHub는 서버가 존재하지 않을 때 해당 이력의 복사본을 보관하며, 빌드와 코드 리뷰를 수행하는 공간입니다. 이 가이드는 빈 폴더에서 시작하여 서버에 배포하기까지의 과정을 하나의 예시로 따라가며, 새로운 용어가 나올 때마다 그 정의를 설명합니다.
Git이 자체적으로 수행하는 작업
Git은 버전 관리 시스템입니다. 디렉터리의 상태를 시간 흐름에 따라 기록하므로 무엇이, 언제, 왜 변경되었는지 확인할 수 있습니다. Git은 2005년 Linux 커널 작업을 위해 작성되었습니다. Git은 분산형 시스템이며, 이는 저장소의 모든 복사본이 전체 이력을 보유함을 의미합니다. 설계상 중앙 서버는 존재하지 않습니다. 동료의 노트북에 있는 복사본도 서버와 동일하게 완전한 복사본입니다.
Git을 설치하고 사용자 정보를 설정하십시오. Git은 커밋 자체에 이름과 이메일 주소가 기록되기 때문에, 이 정보 없이는 커밋 기록을 거부합니다.
sudo apt update && sudo apt install -y git
git --version
git config --global user.name "Your Name"
git config --global user.email "you@example.com"Ubuntu 24.04에서 git --version을 실행하면 git version 2.43.0가 출력됩니다. 최근 몇 년간 출시된 모든 릴리스는 아래의 모든 작업에 대해 동일하게 동작합니다.
예제: VPS 배포 파일을 위한 저장소
저장소(repository), 줄여서 "repo"는 Git이 추적하는 디렉터리를 의미합니다. git init을 실행하면 해당 디렉터리가 저장소로 변하며, 내부에 숨겨진 .git 폴더가 생성됩니다. 이 폴더가 바로 저장소 그 자체입니다. .git를 삭제하면 기록이 없는 일반 디렉터리만 남게 됩니다.
mkdir vps-deploy && cd vps-deploy
git init -b main
printf '.env\n*.key\n' > .gitignore-b main은 첫 번째 브랜치 이름을 main로 지정합니다. 이 옵션을 생략하면 Git은 기본 브랜치 이름에 대한 긴 안내 문구를 출력합니다. .gitignore은 Git이 절대 추적해서는 안 되는 경로를 나열합니다. 첫날부터 비밀 파일 이름을 이 파일에 기록하십시오. 한 번 커밋된 파일은 삭제하더라도 기록에 남으며, 이를 완전히 제거하려면 이후의 모든 커밋을 다시 작성해야 하기 때문입니다.
커밋: 역사의 단위
이제 스크립트를 추가하고 기록해 봅니다.
printf '#!/bin/sh\nsudo systemctl restart caddy\n' > restart.sh
git add restart.sh .gitignore
git commit -m "Add restart script and gitignore"
git log --onelinegit add은 변경 사항을 스테이징 영역(staging area)으로 옮기며, 이는 다음 커밋에 포함될 항목들의 목록입니다. git commit은 해당 목록을 하나의 항목으로 기록하여 역사에 남깁니다. 커밋(commit)은 추적 중인 모든 파일의 스냅샷, 메시지, 작성자, 타임스탬프, 그리고 이전 커밋을 가리키는 포인터를 포함합니다. git log --oneline은 커밋당 한 줄씩 출력하며, 각 줄은 a1b2c3d와 같은 짧은 해시로 시작합니다. 이 해시는 커밋의 이름이며, 거의 모든 Git 명령에서 이를 사용할 수 있습니다.
git add 단계를 건너뛰고 git commit를 실행하면 no changes added to commit (use "git add" and/or "git commit -a")가 응답합니다. 아무것도 고장 난 것이 아닙니다. Git은 스테이징 영역이 비어 있어 스냅샷을 찍을 대상이 없다고 알려주는 것입니다. git status은 길을 잃었을 때 언제든 실행할 수 있는 명령으로, 현재 브랜치, 스테이징된 변경 사항, 그리고 Git이 인식하고 있으나 추적하지 않는 파일들을 보여줍니다.
브랜치: 두 번째 이력 라인
브랜치는 커밋을 가리키는 이동 가능한 포인터입니다. main은 브랜치이며, Git에서 특별한 의미를 갖지 않습니다. 브랜치를 생성해도 파일이 복사되는 것이 아니라 새로운 포인터만 작성되므로 비용이 발생하지 않습니다.
git switch -c add-backup
printf '#!/bin/sh\nrestic backup /srv\n' > backup.sh
git add backup.sh
git commit -m "Add nightly backup"
git switch main
lsgit switch main을 실행한 후, backup.sh가 목록에서 사라집니다. 아무것도 삭제된 것은 아닙니다. 해당 파일은 add-backup 브랜치에 존재하지만, main에는 원래 없었기 때문에 브랜치를 이동할 때 Git이 작업 디렉터리에서 해당 파일을 제거한 것입니다. 이는 처음 접하는 사용자들에게 흔히 혼란을 줍니다. git switch add-backup를 실행하면 다시 파일이 나타납니다.
원격 저장소: GitHub의 등장
지금까지는 네트워크 연결이 없는 단일 머신에서 모든 작업을 수행했습니다. 원격 저장소(remote)는 동일한 저장소의 다른 복사본을 가리키는 이름이 붙은 URL입니다. GitHub는 이러한 복사본 중 하나를 호스팅합니다. 주 원격 저장소의 관례적인 이름은 origin입니다.
GitHub 웹사이트에서 빈 저장소를 생성한 뒤 연결하십시오. 이때 HTTPS보다는 SSH를 사용하는 것이 좋습니다. SSH 키는 사용자가 직접 관리하는 파일이며, 개인 액세스 토큰(personal access token)처럼 만료되지 않기 때문입니다.
ssh-keygen -t ed25519 -C "vps-deploy"
cat ~/.ssh/id_ed25519.pub
ssh -T git@github.com출력된 공개 키를 복사하여 GitHub 계정의 SSH 키 페이지에 붙여넣은 다음, 테스트를 다시 실행하십시오. 키가 정상적으로 작동하면 Hi yourname! You've successfully authenticated, but GitHub does not provide shell access.라는 응답이 옵니다. GitHub는 셸 접근을 허용하지 않으므로, 이 거부 메시지는 성공을 의미합니다. git@github.com: Permission denied (publickey).가 나타난다면 키가 전달되지 않았거나 수락되지 않은 것이므로, .pub 파일을 올바르게 붙여넣었는지, 그 옆에 있는 개인 키를 실수로 사용하지 않았는지 확인하십시오.
git remote add origin git@github.com:yourname/vps-deploy.git
git push -u origin maingit push은 로컬 커밋을 원격 저장소로 전송합니다. -u은 로컬 main가 원격 main을 추적하도록 설정하므로, 이후에는 단순히 git push만 입력해도 됩니다. git clone <url>는 새로운 머신에서 사용하는 반대 과정입니다. 저장소 전체와 그 기록을 복사하고 origin을 자동으로 설정해 줍니다. HTTPS 원격 저장소도 사용할 수 있으며, 이는 일반 웹 페이지와 동일한 프로토콜을 사용하므로 22번 포트의 아웃바운드 연결이 차단된 네트워크에서 유용합니다. 이 문장에 대한 자세한 설명이 필요하다면 HTTP 요청의 실제 구성 요소를 참조하십시오.
Pull request, issue, fork: GitHub의 기능이며 Git의 기능이 아님
위에서 설명한 모든 내용은 Git에 해당하며, 어떤 서버에서든 동일하게 작동합니다. 아래의 세 가지 용어는 GitHub의 고유 기능입니다. 다른 호스팅 서비스들이 이를 모방하고 있지만, Git 자체는 이 개념들을 알지 못합니다.
Pull request(PR)는 한 브랜치를 다른 브랜치로 병합해 달라는 요청이며, 토론을 위한 페이지가 함께 제공됩니다. 사용자가 add-backup를 푸시하고 main를 대상으로 PR을 열면, 사이트에서 커밋 단위로 변경 사항을 보여줍니다. 사람들은 특정 코드 라인에 댓글을 달 수 있습니다. 자동화된 검사 도구는 해당 브랜치의 통과 여부를 보고합니다. 병합 버튼을 클릭하면 GitHub가 자체 복사본에서 병합을 수행한 뒤 main을 업데이트합니다. 이 명칭은 관리자에게 자신의 브랜치를 가져가 달라고(pull) 요청하던 초기 워크플로우에서 유래했습니다.
Issue는 버그나 작업을 추적하기 위한 번호가 매겨진 스레드입니다. 이는 저장소가 아닌 GitHub의 데이터베이스에 저장됩니다. 호스팅 업체를 선택하기 전에 이 점을 알아두어야 합니다. 저장소를 클론하면 모든 커밋은 가져올 수 있지만, 이슈는 하나도 가져올 수 없습니다. 이슈를 추출하려면 API를 호출해야 합니다.
Fork는 타인의 저장소를 자신의 서버 측에 복사해 둔 것입니다. 복사본에 대한 쓰기 권한을 가지게 되며, 브랜치를 푸시한 뒤 자신의 복사본에서 원본 저장소로 pull request를 보낼 수 있습니다. 이것이 관리자가 나를 알지 못하는 프로젝트에 기여하는 방식입니다. 포크는 GitHub 상에 존재하며 원본 저장소의 위치를 기억하는 클론입니다.
소프트웨어는 사람이 사용하는 것과 동일한 API를 통해 이 세 가지를 읽습니다. 자체 서버에서 실행하는 pull request 검토 에이전트는 새로운 PR을 감시하고, diff를 읽고, 코드 라인에 댓글을 작성합니다. 저장소 루트의 AGENTS.md 파일과 같은 관례가 존재하는 이유는 이제 저장소가 사람뿐만 아니라 도구에 의해서도 읽히기 때문입니다.
VPS 소유자에게 GitHub가 실제로 하는 역할
서버 외부 저장소를 활용하십시오. 배포 스크립트와 플레이북은 이를 실행할 서버가 아닌 다른 곳에 두어야 합니다. VPS를 초기 이미지에서 다시 빌드하고, 복제한 뒤 실행하십시오. 해당 저장소를 비공개로 유지하고 서버에 deploy key를 부여하십시오. 이는 계정 전체가 아닌 특정 저장소 하나에만 등록되는 SSH 키이며, 읽기 전용으로 설정해야 합니다. 읽기 전용 deploy key가 유출되면 해당 저장소 하나만 노출되지만, 계정 키가 유출되면 푸시 권한이 있는 모든 저장소가 노출됩니다.
sudo git clone git@github.com:yourname/vps-deploy.git /srv/vps-deploy
cd /srv/vps-deploy
git pull --ff-only--ff-only은 머지 커밋 생성을 거부합니다. 변경 사항을 가져오기만 하는 서버에서 머지는 항상 실수이므로, 이 플래그는 혼란스러운 기록을 명확한 오류인 fatal: Not possible to fast-forward, aborting.로 바꿉니다. 서버에서 변경되지 말아야 할 내용이 변경되었습니다. 다시 pull을 수행하기 전에 원인을 찾으십시오.
root 권한으로 복제한 뒤 다른 사용자로 Git을 실행하면 fatal: detected dubious ownership in repository at '/srv/vps-deploy'가 발생합니다. 악의적인 .git/config이 Git 명령을 실행할 수 있기 때문에, Git은 다른 사용자가 소유한 저장소 읽기를 거부합니다. safe.directory 예외를 추가하면 근본 원인을 제거하지 않은 채 검사만 무력화되므로, 대신 chown을 사용하여 소유권을 수정하십시오.
GitHub Actions: 빌드 및 배포 파이프라인
Actions는 GitHub의 CI/CD(지속적 통합 및 지속적 배포) 시스템입니다. .github/workflows/ 경로 아래에 YAML 파일을 커밋하면 지정한 이벤트가 발생할 때 GitHub가 해당 파일을 실행합니다.
name: check
on:
push:
branches: [main]
jobs:
shellcheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- run: sudo apt-get update && sudo apt-get install -y shellcheck
- run: shellcheck *.sh이 파일은 워크플로우(workflow)입니다. 잡(job)은 하나의 머신에서 실행됩니다. 단계(step)는 하나의 명령어 또는 게시된 액션을 의미합니다. uses:는 다른 저장소에서 액션을 가져오며, @v7는 해당 액션의 메이저 버전을 고정합니다(2026년 8월 기준 actions/checkout의 최신 버전은 v7입니다). 고정되지 않은 액션은 검토하지 않은 코드가 비밀 정보에 접근하여 실행될 수 있으므로 항상 버전을 고정하십시오.
runs-on: ubuntu-latest은 GitHub에 새로운 가상 머신을 요청하며, 잡이 종료되면 해당 머신은 폐기됩니다. 표준 러너는 공개 저장소에서 무료로 제공되며, 2026년 8월 기준 무료 플랜에는 비공개 저장소를 위한 월 2,000분의 실행 시간이 포함되어 있습니다. 해당 수치를 기준으로 예산을 책정하기 전에 현재 요금 페이지를 확인하십시오.
비밀 정보는 저장소 설정에 저장되며 ${{ secrets.DEPLOY_KEY }}로 읽어옵니다. 포크된 저장소에서 보낸 풀 리퀘스트(pull request)에 의해 트리거된 워크플로우는 읽기 전용 토큰만 부여받으며 비밀 정보에 접근할 수 없습니다. 그렇지 않으면 외부 사용자가 비밀 정보를 출력하기만 하는 PR을 생성할 위험이 있기 때문입니다.
자체 VPS에서 Actions runner 실행하기
runs-on: self-hosted는 작업을 사용자가 소유한 머신으로 전달합니다. 리포지토리의 runner 설정 페이지에서 다운로드 명령, 리포지토리 웹 주소, 1시간 동안 유효한 등록 토큰을 제공합니다. 마지막 두 항목을 REPO_URL 및 RUNNER_TOKEN에 입력하면, 설정은 세 가지 명령어로 완료됩니다.
./config.sh --url "$REPO_URL" --token "$RUNNER_TOKEN"
sudo ./svc.sh install
sudo ./svc.sh start
./svc.sh statussvc.sh status는 서비스가 활성 상태임을 보고하고 최근 로그 라인을 표시해야 합니다. runner는 GitHub로 아웃바운드 HTTPS 연결을 열어 작업을 요청하므로, 인바운드 포트를 열 필요는 없습니다. svc.sh install은 systemd unit을 작성하는데, 이는 사람들이 흔히 건너뛰는 단계입니다. 이 단계를 수행하지 않으면 SSH 세션이 종료될 때 runner도 함께 종료되며, 이후의 모든 작업은 아무런 설명 없이 대기열에 머물게 됩니다. VPS에서의 전체 자체 호스팅 runner 설정 가이드는 장기 실행 runner에 필요한 보안 강화 및 정리 작업을 안내합니다.
이 방식의 장점은 작업이 이미 해당 머신에서 실행 중이므로, 배포를 위해 인터넷에서 접근 가능한 인바운드 SSH 키가 더 이상 필요하지 않다는 것입니다. 또한 빌드 캐시가 실행 간에 유지되며, 사용 시간(minute meter)도 차감되지 않습니다.
한 가지 필수적인 경고가 있습니다. GitHub의 공식 문서에서는 자체 호스팅 runner를 비공개 리포지토리에만 사용할 것을 권장합니다. 공개 리포지토리를 포크한 사용자가 풀 리퀘스트를 열어 runner에서 위험한 코드를 실행할 수 있기 때문입니다. runner는 해당 브랜치의 워크플로우 파일에 명시된 모든 내용을 실행합니다. 푸시 권한을 제어할 수 있는 비공개 리포지토리에서는 위험이 작지만, 공개 리포지토리에서는 자체 호스팅 runner를 낯선 사람이 코드를 실행할 수 있는 머신으로 간주해야 합니다.
GitHub이 반드시 필요한가?
아니요. Git은 표준이며, GitHub은 편의를 위한 서비스일 뿐입니다. Forgejo와 Gitea는 자체 호스팅이 가능한 코드 저장소(forge)이며, 코드 저장소란 이슈와 풀 리퀘스트 기능을 갖춘 Git 호스트를 의미합니다. 두 서비스 모두 단일 Go 바이너리로 배포되며 소규모 VPS에서 실행할 수 있습니다. Forgejo는 2022년에 Gitea에서 포크(fork)된 프로젝트로, 현재 Codeberg를 운영하는 기반이기도 합니다. Git의 통신 프로토콜은 동일하므로 저장소를 옮기는 작업은 명령어 한 줄로 가능합니다.
git remote -v
git remote set-url origin git@git.example.com:you/vps-deploy.git
git push origin main모든 커밋은 이전할 수 있습니다. 모든 클론(clone)에 전체 기록이 포함되어 있기 때문입니다. 이전되지 않는 것은 GitHub이 그 위에 구축한 계층인 이슈와 풀 리퀘스트 스레드입니다. CI(지속적 통합) 역시 자동으로 이전되지 않습니다. Forgejo는 .forgejo/workflows/의 YAML과 유사한 형식을 읽는 자체 Actions 구현체를 가지고 있습니다. Forgejo의 문서는 GitHub Actions와 Forgejo Actions가 동일하지 않으며 즉시 작동하지 않을 수 있다는 한계를 명확히 밝히고 있습니다. 또한 별도의 러너(runner)도 필요합니다. 이 단계는 단순 복사가 아닌 포팅(porting) 작업으로 계획해야 합니다.
대부분의 프로젝트가 GitHub에 머무는 솔직한 이유는 기여자 때문입니다. 공개 코드는 사람들이 이미 계정을 가지고 있는 곳에 두어야 합니다. 하지만 비공개 배포 스크립트는 그럴 필요가 없습니다. 이 두 가지는 별개의 결정이며, 각각 다르게 판단해도 괜찮습니다.
무엇이 먼저 실패하며, 오류 메시지는 무엇을 의미하는가
푸시가 거부되었습니다. 다음과 같은 메시지가 나타납니다:
! [rejected] main -> main (fetch first)
error: failed to push some refs to 'github.com:yourname/vps-deploy.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally.마지막으로 풀(pull)을 수행한 이후 누군가 푸시를 했습니다. 웹 에디터에서 직접 수정한 내용일 가능성이 높습니다. git pull --rebase를 실행하여 상대방의 커밋 위에 본인의 커밋을 다시 적용한 뒤, 다시 푸시하십시오. 공유 브랜치에서 git push --force을 사용하는 것은 피해야 합니다. 서버에 있는 다른 사람의 커밋을 삭제해 버리기 때문입니다.
fatal: refusing to merge unrelated histories. 로컬에서 git init을 실행함과 동시에 GitHub에서 README 파일을 포함하여 저장소를 생성했습니다. 두 기록은 공유하는 커밋이 없으므로 Git은 이를 자동으로 병합하지 못합니다. 가장 깔끔한 해결책은 GitHub의 저장소를 새 폴더에 클론(clone)한 뒤, 기존 파일을 그 안으로 옮기는 것입니다.
error: src refspec main does not match any. 지정한 브랜치가 현재 저장소에 존재하지 않습니다. 보통 저장소에 커밋이 하나도 없거나, 브랜치 이름이 master인 경우에 발생합니다. git branch --show-current을 실행하면 해결됩니다.
비밀 정보가 커밋에 포함되었습니다. 즉시 해당 자격 증명을 교체하십시오. 푸시된 순간부터 해당 정보는 공개된 것으로 간주해야 합니다. 포크(fork), 미러(mirror), 캐시된 뷰 등에 복사본이 남아 있어 삭제할 방법이 없기 때문입니다.
FAQ
GitHub과 Git은 같은 것입니까?
아닙니다. Git은 컴퓨터에 설치하여 네트워크나 계정 없이도 작동하는 버전 관리 프로그램입니다. GitHub은 Git 저장소를 저장하고 웹 인터페이스, 이슈, 풀 리퀘스트, CI 기능을 추가로 제공하는 상용 호스팅 서비스입니다. Git은 2005년에 출시되었고, GitHub은 그 위에서 2008년에 시작되었습니다. GitHub 없이도 Git을 영구적으로 사용할 수 있습니다. 모든 GitHub 기능은 기반이 되는 Git에 의존합니다.
VPS에서 Git을 사용하려면 GitHub 계정이 필요합니까?
아닙니다. git init, git commit, git log는 원격 저장소 설정 없이도 서버에서 작동하며, 이는 /etc 파일의 변경 사항을 추적하거나 스크립트를 배포하기에 충분합니다. 계정은 서버가 손실되어도 유지되는 기록 사본이 필요하거나, 다른 컴퓨터에서 복제(clone)해야 할 때 유용합니다. Forgejo나 Gitea 같은 자체 호스팅 포지(forge) 소프트웨어는 소유한 하드웨어에서 동일한 요구 사항을 충족하며, 다른 장비의 베어 저장소를 가리키는 단순한 SSH 원격 설정만으로도 별도의 포지 소프트웨어 없이 운영할 수 있습니다.
풀 리퀘스트(pull request)란 무엇입니까?
풀 리퀘스트는 한 브랜치를 다른 브랜치로 병합해 달라는 요청이며, 토론 페이지가 함께 제공됩니다. 브랜치를 푸시하고 main을 대상으로 PR을 열면, 호스트는 커밋 단위로 변경 사항을 보여주어 검토자가 개별 코드 라인에 의견을 남기고 자동화된 검사가 성공 또는 실패를 보고할 수 있게 합니다. 이는 Git의 기능이 아니라 GitHub의 기능이므로, Git 자체에는 이를 위한 명령어가 없습니다. 다른 호스트들도 같은 개념을 구현하며, 때로는 이를 머지 리퀘스트(merge request)라고 부르기도 합니다.
제 VPS에서 GitHub Actions 러너를 실행해야 합니까?
비공개 저장소의 경우, 대개 그렇습니다. 작업이 이미 비용을 지불 중인 하드웨어에서 실행되므로 사용 시간이 측정되지 않고, 빌드 캐시가 유지되며, 러너가 GitHub으로 아웃바운드 연결을 시도하여 작업을 가져오기 때문에 배포를 위해 인터넷에 인바운드 SSH 키를 노출할 필요가 없습니다. 공개 저장소의 경우, GitHub은 이를 권장하지 않습니다. 누구나 저장소를 포크(fork)하여 귀하의 머신에서 코드를 실행하는 워크플로우가 포함된 풀 리퀘스트를 열 수 있기 때문입니다.
나중에 GitHub에서 저장소를 옮길 수 있습니까?
코드 자체는 쉽게 옮길 수 있습니다. 모든 복제본(clone)은 전체 기록을 보유하고 있으므로, git remote set-url origin <new url> 후 푸시를 수행하면 커밋에 포함된 모든 내용을 옮길 수 있습니다. GitHub이 소유한 계층인 이슈, 풀 리퀘스트 토론, Actions 기록은 .git 폴더가 아닌 GitHub의 데이터베이스에 남아 있습니다. 마이그레이션 도구를 사용하여 API를 통해 이슈를 복사할 수 있으며, 워크플로우 파일은 일반적으로 새로운 호스트의 CI에 맞게 수정해야 합니다. 이를 고려하여 실제 문서는 이슈 스레드가 아닌 저장소 내부에 보관하는 것이 좋습니다.