어두운 북유럽풍 석조 금고 안, 서버 랙 옆에 서 있는 NordBastion 북극곰 마스코트. 한쪽 발은 세 갈래가 합쳐지는 Git 이력을 나타내는 청록색 홀로그램 브랜치 다이어그램 위에 얹혀 있고, 옆 작업대에는 룬 문자가 새겨진 소스 코드 금고가 놓여 있으며, 높은 아치형 창문으로 오로라 빛이 쏟아져 들어온다
가이드 · 개발자 인프라·읽는 데 18분 · 실습 45분

여러분만의 Git 서버.
저장소가 곧 조직도다.

비공개 저장소는 여러분이 무엇을 만들고 있는지, 누구와 함께 만들고 있는지, 얼마나 진행되었는지를 날짜별로 기록한 자료입니다 — 그리고 플랫폼에서는 그 기록을 다른 누군가의 관할권에 있는 회사가 쥐고 있습니다. 이를 여러분이 익명으로 임대한 서버로 옮기는 여섯 단계: Caddy 뒤의 Forgejo, 그 아래의 PostgreSQL, 전용 포트를 통한 SSH 푸시, 프로덕션 시크릿을 절대 보지 않는 Actions 러너, 그리고 실제로 복원해 본 백업. 서버 비용은 월 $3.90입니다. Debian 12에서 테스트했습니다.

6단계
  1. 01

    선택

    베어 저장소, Forgejo, GitLab

  2. 02

    프로비저닝

    부팅 전에 결정된 포트

  3. 03

    배포

    Forgejo, Postgres, Caddy

  4. 04

    푸시

    SSH, 배포 키, 서명

  5. 05

    빌드

    거리를 둔 러너

  6. 06

    Keep

    미러, 백업, 업그레이드

시작하기 전에 · 플랫폼이 쥐고 있는 것

코드는 가장 흥미롭지 않은 부분이다. 메타데이터가 자산이다.

Git을 셀프 호스팅하는 것에 대한 논쟁은 거의 다 소스 코드에 관한 논쟁이지만, 정작 소스 코드는 가장 덜 중요한 부분이다. 대부분은 특별할 것이 없고, 상당 부분은 오픈소스 라이브러리 위에 얹은 얇은 층에 불과하며, 내일 당장 유출되더라도 실질적인 피해는 대개 손해가 아니라 민망함 정도일 것이다. 그래서 "내 코드는 비밀이 아니다"라는 말이 완전한 답처럼 느껴지지만, 실은 그렇지 않다.

코드를 둘러싼 것이야말로 흥미로운 부분이다. 저장소는 날짜가 찍히고 작성자가 특정된 고해상도 업무 기록이다. 커밋 타임스탬프는 여러분이 언제 잠을 자는지 말해주고, 공동 작성자 항목은 누구와 함께 일하는지 말해주고, 브랜치 이름은 아직 아무도 발표하지 않은 제품을 말해주고, 이슈 트래커는 무엇이 얼마나 심각하게 망가져 있는지 말해주며, 첫 커밋은 프로젝트 전체의 시작일을 알려준다. 이런 식으로 저장소 백 개를 읽다 보면 더 이상 소프트웨어를 읽는 것이 아니다 — 하나의 회사와 그 로드맵, 인원 규모, 골칫거리를 읽고 있는 것이다.

플랫폼에서는 그 기록이 어떤 의미 있는 차원에서도 여러분의 것이 아니다. 그것은 여러분이 통제하지 않는 인프라 위에, 여러분이 인증한 신원과 연결된 계정 아래, 사법 절차가 미치는 관할권 안에 놓여 있으며, 언제든 개정될 수 있는 문서가 그 범위를 정하는 시스템에 의해 스캔된다. 이 중 어느 것도 누군가의 악의를 필요로 하지 않는다. 그저 이슈 트래커를 임대한다는 것의 의미일 뿐이다. 네 가지 방식과, 각각이 실제로 무엇을 내주는지 살펴보자:

저장소가 위치하는 곳 이력을 쥐고 있는 주체 법적 신원과 연결됨 잘못될 수 있는 방식
플랫폼, 개인 계정플랫폼, 자국 관할권 안에서예 — 이메일, 결제 정보, 대개 전화번호까지정지 한 번으로 모든 원격 저장소가 함께 사라짐
플랫폼, 조직 계정플랫폼, 그리고 그 조직을 관리하는 누구든예, 그리고 소속 회사에도퇴사 처리로 자신의 작업물에 대한 접근 권한도 사라짐
셀프 호스팅, 하이퍼스케일러 위에서여러분, 애플리케이션 계층에서예 — 결제 계정 자체가 신원스냅샷이 폐쇄될 수 있는 계정 안에 존재함
셀프 호스팅, KYC 없는 VPS 위에서여러분, 오직 여러분뿐아니오 — 신분증도, 카드도 없이 암호화폐로 결제이제 여러분이 시스템 관리자다. 잘못된 백업은 되돌릴 수 없다

세 번째 행은 이미 이 문제를 해결했다고 생각하는 팀들이 걸려드는 지점이다. 인증된 법인 계정 아래 회사 카드로 임대한 머신에 직접 설치한 포지는 애플리케이션을 플랫폼 밖으로 옮겼을 뿐, 신원 노출은 정확히 그대로 남겨둔다. 이는 통제력에서는 진짜 이득이지만, 노출 면에서는 아무런 이득도 아니다.

그리고 네 번째 행은 그 대가를 있는 그대로 밝힌다. 이 가이드의 나머지 부분은 대부분 그 대가를 치르는 방법에 관한 것이기 때문이다. 셀프 호스팅의 실패 양상은 감시가 아니라 상실이다. 플랫폼은 여러분이 신경 쓸 필요조차 없는 백업을 알아서 유지한다. 여러분의 서버는 오직 여러분이 설정한 백업만 유지한다. 6단계는 마지막에 덧붙이는 선택적 마무리가 아니다 — 나머지 다섯 단계를 안전하게 만드는 단계다.

단계 01 · 선택

포지가 얼마나 필요할까? 정직한 답은 흔히 "없음"이다.

문제를 해결하는 가장 작은 것부터 시작하십시오. 그 위에 얹는 모든 계층은 여러분이 운영해야 하는 서비스이기 때문입니다. SSH를 통한 베어 저장소는 이미 가지고 있는 서버에서 명령 하나면 끝나며, 인증과 이력, 푸시 권한을 갖춘 진짜 Git 원격 저장소를 제공합니다. 웹 애플리케이션도, 데이터베이스도, 끝없는 업그레이드도 없고, sshd 외에는 패치할 것도 없습니다. 커밋을 노트북과 플랫폼 밖으로 꺼내고 싶은 1인 사용자라면 이것으로 완전한 답이 되며, 2단계 이후로는 더 읽지 않아도 됩니다.

# On the server, as an unprivileged user:
mkdir -p ~/git/project.git && cd ~/git/project.git
git init --bare

# On your laptop:
git remote add origin ssh://user@git.example.com/~/git/project.git
git push -u origin main

두 번째 사람이 관여하거나, 베어 저장소 디렉터리로는 할 수 없는 네 가지 — 브라우저로 이력 살펴보기, 풀 리퀘스트로 변경 사항 리뷰하기, 코드 옆에서 이슈 추적하기, 한 사람에게 저장소 하나에 대한 접근 권한 부여하기 — 중 하나라도 원할 때 포지가 필요해집니다. 이는 기능 면에서도 진짜 발전이고 운영 부담 면에서도 진짜 증가입니다. 신중하게 선택하십시오:

속성 SSH를 통한 베어 저장소 Forgejo Gitea GitLab CE
웹 UI, 풀 리퀘스트, 이슈없음예예예, 그리고 훨씬 더 많이
유휴 상태 메모리 사용량사실상 0수백 MB수백 MBCI 없이도 수 GB
내장 CI직접 작성한다면 git hooksForgejo ActionsGitea ActionsGitLab CI, 가장 성숙한 것
프로젝트를 조종하는 주체Git 프로젝트비영리 단체 Codeberg e.V.영리 기업, 오픈 코어상장 기업, 오픈 코어
라이선스GPLv2v9.0부터 GPLv3+ (그 이전은 MIT)MIT, 독점 애드온 포함MIT 코어, 독점 티어
적합한 경우혼자이고 유지보수를 전혀 하고 싶지 않을 때작은 서버에서 제대로 된 포지를 원할 때이미 운영 중이고 잘 작동할 때엔터프라이즈 기능 세트가 필요할 때

Forgejo와 Gitea는 조상이 같아서 일상적인 사용에서는 여전히 거의 똑같아 보입니다. Forgejo는 2024년 초 하드 포크가 되었고 베를린에 등록된 비영리 협회인 Codeberg e.V.가 소유하고 있으며, Gitea는 상표를 소유하고 기여자에게 저작권 양도를 요구하는 영리 기업이 개발합니다. 둘 다 좋은 소프트웨어입니다. 다만 그중 관리 주체의 이해관계가 여러분이 이 페이지를 읽는 이유와 맞아떨어지는 쪽은 하나뿐입니다.

이 가이드의 나머지 부분은 Forgejo를 사용합니다. 2단계부터 6단계까지의 내용은 Gitea에도 거의 그대로 적용됩니다 — 컨테이너 이름과 몇몇 환경 변수만 바뀔 뿐, 전체적인 형태는 달라지지 않습니다.

단계 02 · 프로비저닝

포지가 대신 정하게 두지 말고, 포트를 먼저 정하십시오. 그중 둘은 SSH입니다.

먼저 서버부터 주문하십시오. 패널에서: Order → VPS → Sentinel, 이미지는 Debian 12, 이력이 자리 잡길 원하는 거점 — Helsinki, Stockholm, Oslo, Reykjavík 중 어디든 좋습니다. 계정을 개설하는 데 이메일 주소가 필요 없고, 청구서는 Monero, Bitcoin, Lightning을 비롯한 다른 지원 자산으로 결제됩니다. 이는 대부분의 워크로드보다 여기서 더 중요합니다. 포지는 여러분 작업의 기록이 어떤 이름과도 연결되지 않는다는 것 자체가 가치 제안의 전부인 서비스이기 때문입니다.

그런 다음 첫 시간 하드닝 체크리스트를 실행하십시오 — 옮긴 포트에서의 키 전용 SSH, 무인 업그레이드, 기본 거부 방화벽. 아래의 모든 내용은 아직 다른 누군가의 것이 되지 않은 서버를 전제로 하기 때문입니다.

여러분이 실제로 인터넷에 올리는 것

포지는 로그인이 달린 정적 사이트가 아닙니다. 코드 실행 엔진이 붙어 있는 공개 웹 애플리케이션입니다. 그대로 방치된 두 가지 기본값이 이를 낯선 사람들을 위한 무료 서비스로 바꿔놓습니다. 하나는 개방된 가입으로, 인증서가 투명성 로그에 나타난 지 며칠 만에 인스턴스를 스팸 계정으로 채웁니다. 다른 하나는 풀 리퀘스트를 열 수 있는 누구의 워크플로든 실행하는 CI로, 이것이 셀프 호스팅한 러너가 결국 남을 위해 암호화폐를 채굴하게 되는 경로입니다. 둘 다 은밀한 공격이 아니며 둘 다 드물지도 않습니다. 둘 다 3단계와 5단계에서 각각 설정 하나로 차단할 수 있습니다 — 다만 지금 바로 그것들을 차단하기로 마음먹으십시오. "작동한다"와 "악용되고 있다" 사이의 간격은 며칠 단위로 측정되기 때문입니다.

서버에 호스트네임을 지정하십시오 — A 레코드 하나, IPv6로도 접근 가능하게 하려면 AAAA 레코드도 하나 — 그리고 시작하기 전에 전파될 때까지 기다리십시오. 리버스 프록시가 처음 부팅될 때 그 이름으로 인증서를 발급받으려 시도하는데, 챌린지 실패로 시작하면 혼란스럽기 때문입니다. 그런 다음 아직 아무것도 수신 대기하지 않는 동안 방화벽 규칙을 작성하십시오.

두 개의 SSH 문제. 포지는 Git을 위해 SSH를 사용하고, 여러분의 서버는 이미 관리를 위해 SSH를 사용하고 있습니다. 둘 다 22번 포트를 차지할 수는 없으며, 이를 섞어 쓰면 결국 Git까지 이해해야 하는 관리용 데몬이 생기고 맙니다. 이 둘을 분리해 두십시오. 관리용은 하드닝 가이드에서 옮긴 포트에, Git용은 컨테이너로 매핑되는 전용 공개 포트에 두십시오. 2222번이 관례적인 선택이지만 거기에 특별한 의미는 없습니다.

ufw default deny incoming
ufw allow 47222/tcp                  # administrative SSH, moved
ufw allow 80/tcp                     # ACME HTTP-01 challenge
ufw allow 443/tcp                    # the web interface
ufw allow 2222/tcp                   # git over SSH, into the container
ufw enable
ufw status numbered

# Nothing else is open. In particular: PostgreSQL stays on the Docker
# network and is never published to the host, let alone the internet.

배포판 패키지 대신 공식 저장소에서 Docker를 설치하여 Compose v2가 포함되고 버전도 최신 상태를 유지하도록 하십시오. 그런 다음 스택을 위한 디렉터리를 만들고, 서버에서 상태를 보관하는 유일한 장소로 삼으십시오 — 이것이 6단계에서 백업할 디렉터리입니다.

curl -fsSL https://get.docker.com | sh
docker compose version

install -d -m 0750 /srv/forgejo
cd /srv/forgejo
단계 03 · 배포

컨테이너 세 개. 그중 하나는 절대 접근 가능해서는 안 됩니다.

Forgejo, PostgreSQL, Caddy. Forgejo는 저장소를 보관하고 인터페이스를 제공하며, PostgreSQL은 Git 객체가 아닌 모든 것을 보관하고, Caddy는 TLS를 종료하고 두 번 요청할 필요 없이 인증서를 발급받습니다. PostgreSQL은 의도적으로 호스트에 공개되지 않습니다. 그것을 필요로 하는 단 하나의 컨테이너에서만, 이름을 통해, Compose 네트워크로만 접근할 수 있습니다.

/srv/forgejo/.env

FORGEJO_DOMAIN=git.example.com
FORGEJO_VERSION=15                             # the LTS line; 16 is current stable
POSTGRES_PASSWORD=$(openssl rand -base64 32)   # paste the value, do not leave the $( )

/srv/forgejo/docker-compose.yml

services:
  forgejo:
    image: codeberg.org/forgejo/forgejo:${FORGEJO_VERSION}
    container_name: forgejo
    environment:
      USER_UID: 1000
      USER_GID: 1000
      FORGEJO__database__DB_TYPE: postgres
      FORGEJO__database__HOST: db:5432
      FORGEJO__database__NAME: forgejo
      FORGEJO__database__USER: forgejo
      FORGEJO__database__PASSWD: ${POSTGRES_PASSWORD}
      FORGEJO__server__DOMAIN: ${FORGEJO_DOMAIN}
      FORGEJO__server__ROOT_URL: https://${FORGEJO_DOMAIN}/
      FORGEJO__server__SSH_DOMAIN: ${FORGEJO_DOMAIN}
      FORGEJO__server__SSH_PORT: "2222"          # what clones will say
      FORGEJO__server__SSH_LISTEN_PORT: "22"     # what the container listens on
      FORGEJO__service__DISABLE_REGISTRATION: "true"
      FORGEJO__repository__DEFAULT_PRIVATE: private
      FORGEJO__security__INSTALL_LOCK: "true"
    restart: unless-stopped
    volumes:
      - ./data:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    ports:
      - "127.0.0.1:3000:3000"    # web, reached only by Caddy
      - "2222:22"                # git over SSH, public
    depends_on:
      - db

  db:
    image: postgres:16-alpine
    container_name: forgejo-db
    environment:
      POSTGRES_USER: forgejo
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: forgejo
    restart: unless-stopped
    volumes:
      - ./postgres:/var/lib/postgresql/data
    # No ports: block. This is the point.

  caddy:
    image: caddy:2-alpine
    container_name: forgejo-caddy
    restart: unless-stopped
    network_mode: host
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - ./caddy-data:/data
      - ./caddy-config:/config

/srv/forgejo/Caddyfile

git.example.com {
    encode zstd gzip
    request_body {
        max_size 512MB          # raise it if you push large repositories
    }
    reverse_proxy 127.0.0.1:3000
}

이 두 SSH 관련 줄은 사람들이 흔히 잘못 설정하는 부분이니 두 번 읽어볼 가치가 있습니다. SSH_LISTEN_PORT는 컨테이너 내부의 포트로, Forgejo의 내장 SSH 서버가 바인딩하는 곳입니다. SSH_PORT는 Forgejo가 보여주는 클론 URL에 표시하는 포트로, 포트 매핑의 공개 측과 일치해야 합니다. 이 둘이 어긋나면 사용자가 복사해서 붙여넣는 그 한 가지만 빼고 나머지는 전부 작동합니다.

띄운 다음, 명령줄에서 관리자를 생성하십시오. 웹 설치 프로그램은 사용하지 마십시오. 새 인스턴스에서는 호스트네임을 찾아낸 누구든 그것에 접근할 수 있고, 그것을 통해 처음 생성된 계정이 관리자가 됩니다 — 굳이 뛰어들 필요 없는 경쟁입니다. 위 파일의 INSTALL_LOCK이 컨테이너가 시작되기도 전에 이를 차단합니다.

docker compose up -d
docker compose logs -f forgejo        # wait for "Starting new Web server"

docker compose exec -u git forgejo forgejo admin user create \
  --admin --username you --email you@example.com --random-password

브라우저에서 호스트네임을 열고, 출력된 비밀번호로 로그인한 다음 바로 변경하십시오. 그런 다음 작동하는 인스턴스를 폐쇄된 인스턴스로 바꿔주는 설정들을 하나씩 살펴보십시오 — 대부분은 위의 환경 변수 블록에서 이미 설정되어 있지만, 그냥 넘겨짚기보다는 하나하나 확인해 볼 가치가 있습니다:

가입

DISABLE_REGISTRATION = true. 계정은 여러분이 직접 만듭니다. 누구도 스스로 가입할 수 없습니다. 이따금 가입이 필요하다면, 문을 아예 열어두는 대신 초대 절차를 이용하십시오.

기본 공개 범위

DEFAULT_PRIVATE = private. 새 저장소는 비공개로 시작합니다. 그러면 공개는 기본값의 결과가 아니라 의도적인 행위가 됩니다.

익명 열람

REQUIRE_SIGNIN_VIEW = true는 로그아웃한 방문자에게는 공개 저장소조차 숨깁니다. 비공개 팀 포지에는 적합하지만, 무언가를 공개하고 싶다면 적합하지 않습니다.

2단계 인증

다른 무엇보다 먼저 관리자 계정에서 이를 활성화하십시오. 이제 포지는 여러분이 가진 모든 브랜치를 다시 쓸 수 있는 머신이기 때문입니다.

기억해 둘 만한 경로 하나. 실제 사용되는 설정 파일은 호스트의 ./data/gitea/conf/app.ini에 있습니다. Forgejo는 기존 설치와의 호환성을 위해 gitea라는 디렉터리 이름을 그대로 유지했으며, 이는 누구나 딱 한 번은 놀라게 되는 부분입니다. FORGEJO__section__KEY 형태의 환경 변수는 시작할 때 그 파일에 기록되므로, Compose 파일만으로 전체 설정을 관리할 수 있습니다 — 다만 app.ini를 직접 편집하면, 같은 키에 대한 환경 변수가 다음 재시작 때 그 편집 내용을 덮어씁니다. 둘 중 하나를 선택해서 일관되게 사용하십시오.

단계 04 · 푸시

SSH로 푸시하십시오. 그런 다음 서명하여, 이력이 서버에 의존하지 않도록 하십시오.

프로필 설정에 공개 키를 붙여넣은 다음, 클라이언트에게 포지가 어디에 있는지 알려주십시오. Git이 비표준 포트에 있으므로, SSH 설정 파일에 항목 하나를 추가하는 30초가 아깝지 않습니다 — 그러면 앞으로 입력할 모든 URL에서 포트를 뺄 수 있고 원격 저장소 주소도 이식성이 좋아집니다.

# ~/.ssh/config
Host git.example.com
    Port 2222
    User git
    IdentityFile ~/.ssh/id_ed25519

# Then, in any repository:
git remote set-url origin git@git.example.com:you/project.git
git push -u origin main
ssh -T git@git.example.com        # expect a greeting, not a shell

마지막 명령이 바로 포트 매핑, 컨테이너의 SSH 서버, 그리고 여러분의 키가 서로 맞아떨어지는지 알려주는 명령입니다. Forgejo로부터 인사말이 오면 전체 연쇄가 제대로 작동하는 것입니다. 비밀번호 입력 프롬프트가 뜬다면 대신 호스트의 관리용 sshd에 도달한 것이며, 포트 매핑이 잘못되었다는 뜻입니다.

이제 커밋에 서명하십시오. 이는 대부분의 셀프 호스팅 가이드가 건너뛰는 단계이지만, 셀프 호스팅한 포지에서는 플랫폼에서보다 더 중요합니다. 커밋의 작성자 필드는 자유 텍스트라서, 푸시할 수 있는 사람이라면 누구나 그 안에 여러분의 이름을 써넣을 수 있습니다. 서명은 특정 키가 특정 커밋을 만들었다는 증거이며, 그 커밋이 푸시된 서버가 침해당하거나 이전되거나 폐기된 뒤에도 계속 증거로 남습니다. Git은 이미 가지고 있는 SSH 키로 서명할 수 있으므로, 더 이상 PGP를 거칠 필요가 없습니다:

git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub
git config --global commit.gpgsign true
git config --global tag.gpgsign true

git commit --allow-empty -m "signed"
git log --show-signature -1

그런 다음 포지에 같은 공개 키를 인증 키가 아니라 서명 키로 한 번 더 등록하십시오. Forgejo는 서버 측에서 서명을 검증하여 커밋을 검증됨으로 표시합니다. 이 두 번째 등록이 없으면 서명을 여러분의 계정과 연결할 방법이 없어서, 올바르게 서명되었더라도 모든 커밋이 검증되지 않음으로 표시됩니다.

읽기만 하는 머신을 위한 배포 키. 프로덕션 서버, 빌드 서버, 미러는 포지에 사용자 계정이 필요하지 않습니다. 대신 배포 키를 부여하십시오. 저장소 하나에 추가되는 키 쌍으로, 명시적으로 쓰기 권한을 부여하지 않는 한 읽기 전용이며, 인스턴스의 다른 어떤 것에도 접근 권한을 주지 않습니다. 회수도 항목 하나를 삭제하면 끝입니다. 이는 셀프 호스팅한 포지를 깔끔하게 유지하는 데 가장 효과적인 단 하나의 습관입니다. 사용자 계정은 권한이 쌓이지만 배포 키는 그럴 수 없기 때문입니다.

플랫폼에서 마이그레이션하는 시점이야말로 이 모든 것을 제대로 해 둘 좋은 기회입니다. Forgejo의 New Migration 기능은 저장소 URL과 액세스 토큰만으로 브랜치, 태그, 이슈, 댓글, 레이블, 마일스톤, 릴리스, 풀 리퀘스트까지 가져옵니다. 먼저 저장소 하나로 결과물의 모습을 확인하고, 대량 마이그레이션은 API 속도 제한으로 느려질 것을 예상하며, 원본은 그날 바로 삭제하는 대신 몇 주간 읽기 전용으로 유지하십시오.

단계 05 · 빌드

CI 러너는 낯선 사람의 코드를 실행하는 머신입니다. 그에 맞게 배치하십시오.

Forgejo Actions는 GitHub Actions와 동일한 형식의 워크플로 파일을 .forgejo/workflows/에서 읽어, Forgejo Runner라는 별도의 프로그램에 넘깁니다. 러너는 포지의 일부가 아니며 같은 머신에 있을 필요도 없습니다 — 이것이 설계 전체의 핵심이며, 이 단계에 그냥 넘어갈 수 없는 경고가 따라붙는 이유입니다.

인스턴스에서 Actions를 활성화한 다음, 관리 패널에서 등록 토큰을 발급받으십시오 — 인스턴스 전체 범위로 하거나, 확신이 없을 때 더 나은 기본값인 조직 하나 또는 저장소 하나로 범위를 제한할 수 있습니다. 러너는 포지 쪽으로 나가는 연결만 맺으므로 인바운드 포트가 전혀 필요 없고, 공개 주소가 없는 머신을 포함해 어떤 방화벽 뒤에도 둘 수 있습니다.

# In the Compose environment of the forge, or app.ini:
#   FORGEJO__actions__ENABLED: "true"
# Then: Site administration -> Actions -> Runners -> Create new runner

# On the runner machine:
forgejo-runner generate-config > config.yml

forgejo-runner register --no-interactive \
  --instance https://git.example.com \
  --token <REGISTRATION_TOKEN> \
  --name runner-01 \
  --labels docker:docker://node:22-bookworm,debian:docker://debian:12

forgejo-runner --config config.yml daemon

레이블은 워크플로와 러너 사이의 계약입니다. runs-on: docker를 요청하는 작업은 오른쪽에 있는 이미지로 빌드된 컨테이너를 받습니다. 실제로 제공할 준비가 된 레이블만 선언하십시오. 아무도 제공하지 않는 레이블을 요청하는 작업은 실패하는 대신 영원히 대기하며, 이는 긴 하루의 끝에 디버깅하기 혼란스러운 일이기 때문입니다.

.forgejo/workflows/ci.yml

on: [push, pull_request]

jobs:
  test:
    runs-on: docker
    container:
      image: node:22-bookworm
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm test
차이를 만드는 규칙

러너는 워크플로 파일이 지시하는 대로 무엇이든 실행하며, 그 워크플로 파일은 풀 리퀘스트를 통해 도착합니다. 팀 밖의 누군가라도 풀 리퀘스트를 열 수 있다면, 러너는 포지와는 다른 머신에 있어야 합니다 — 월 $3.90짜리 두 번째 Sentinel이면 되며, 포지의 데이터 디렉터리에 대한 접근 권한도, 데이터베이스 자격 증명도, 빌드 로그에서 읽혀도 곤란한 것도 그 환경에 전혀 없어야 합니다. 포지 자체에서는 처음 기여하는 사람의 워크플로 실행 전에 승인을 요구하십시오. 흔히 벌어지는 두 가지 실패는 둘 다 뻔하면서도 둘 다 비용이 큽니다. 풀 리퀘스트에 숨겨진 채굴 작업, 그리고 누군가 일부러 추가한 단계가 출력하는 시크릿입니다.

시크릿도 같은 방식으로 범위를 제한하십시오. Forgejo는 이를 저장소, 조직, 인스턴스 수준에서 저장합니다. 저장소 하나만 필요로 하는 배포 키는 그 저장소에만 존재해야 합니다. 그래야 관련 없는 프로젝트에서 침해된 워크플로가 그것을 읽을 수 없습니다. 대상이 지원하는 곳이라면 어디서든 장기 토큰보다 단기 토큰을 우선하고, 워크플로 파일에는 절대 시크릿을 넣지 마십시오 — 워크플로 파일은 저장소 안에 있고, 저장소에는 이력이 남기 때문입니다.

빌드가 실행할 때마다 같은 베이스 이미지를 가져온다면 캐싱하십시오. 로컬 레지스트리 미러나 영속적인 러너 캐시는 2분 걸리던 이미지 가져오기를 2초로 줄여주며, 여러분이 두들기고 있는 업스트림 레지스트리에 대한 예의이기도 합니다. 그리고 이 설계 전체가 자연스럽게 이끄는 패턴 하나를 눈여겨보십시오. 이것이 바로 이 가이드가 에이전트 런타임 가이드 옆에 놓인 이유입니다. 러너는 여러분의 포지에서 작업을 가져오는 범용 워커이므로, 예약된 유지보수, 데이터 파이프라인, 에이전트 워크로드도 바로 이곳에 속하게 됩니다.

단계 06 · 보존

모든 클론은 코드의 백업입니다. 그 어떤 것도 대화의 백업은 아닙니다.

Git은 이 지점에서 유별나게 관대하며, 왜 그런지 정확히 짚어볼 가치가 있습니다. 모든 클론은 이력의 완전한 사본이므로, 개발자 한 명이라도 최신 작업 사본을 가지고 있는 한 코드는 서버보다 오래 살아남습니다. 이는 진심으로 안심이 되는 사실이지만, 여러분의 포지가 담고 있는 두 가지 중 정확히 하나만 해당됩니다. 다른 하나 — 이슈, 풀 리퀘스트 논의, 리뷰 스레드, 릴리스, 위키 페이지, 사용자, 팀, 권한, CI 이력 — 는 오직 데이터베이스 안에만 존재하며, 아무도 그 사본을 갖고 있지 않고, 대개 3년에 걸쳐 쌓인 부분이기도 합니다.

먼저, 푸시 미러입니다. 무료이고 알아서 돌아갑니다. 저장소에서: Settings → Mirror settings → 대상의 URL과 액세스 토큰으로 푸시 미러를 추가하십시오. 그러면 Forgejo는 푸시할 때마다 모든 브랜치와 태그를, 두 번째 Forgejo로, Codeberg로, 또는 본거지가 아니라 진열장 정도로 남겨두기로 한 플랫폼으로 복제합니다. 분명히 밝혀둘 주의 사항 하나: 푸시 미러는 이력을 충실하게 다시 게시하며, 여기에는 삭제하려던 시크릿도 포함됩니다.

그다음은 진짜 백업입니다. 내장된 아카이브가 아닌 것으로요. Forgejo는 설정, 저장소, 데이터, 데이터베이스 덤프를 하나의 zip으로 묶는 forgejo dump 명령을 제공합니다. 버그를 신고할 때 메인테이너에게 건네주기에는 정말 유용한 것입니다. 하지만 죽음에서 되살아나기 위해 의지해야 할 것은 아닙니다. 이것이 만들어내는 SQL은 재가져오기 시 오래전부터 알려진 문제가 있고, 이 아카이브는 실행할 때마다 저장소 전체를 다시 쓰는데, 실제 운영 중인 인스턴스에서는 매일 밤 처음부터 대용량 파일을 새로 쓴다는 뜻입니다. 대신 지루하지만 검증 가능한 경로를 사용하십시오 — 데이터베이스는 그 자체의 도구로 덤프하고, 데이터 디렉터리는 파일 그대로 백업하십시오:

#!/bin/sh
# /usr/local/bin/forgejo-backup — run from a systemd timer, nightly
set -eu
cd /srv/forgejo

# 1. Consistent database dump, written next to the data.
docker compose exec -T db pg_dump -U forgejo -Fc forgejo > ./db.dump

# 2. Everything that matters, to an encrypted offsite repository.
restic backup ./data ./db.dump ./docker-compose.yml ./.env ./Caddyfile

# 3. Retention, then prove the repository is still readable.
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 12 --prune
restic check

포지와는 다른 관할권에 있는 머신으로, 추가 전용(append-only) 저장소를 통해 보내십시오. 그러면 포지가 침해당하더라도 자신의 이력을 스스로 지울 수 없습니다 — 암호화된 백업 가이드는 systemd 타이머와 추가 전용 키 제한을 포함해, 위 세 줄을 길게 풀어놓은 버전입니다.

그런 다음 한 번은 일부러 복원해 보십시오. 임시 VPS를 주문해 스냅샷을 복원하고, 같은 Compose 파일을 복원된 데이터 디렉터리에 대해 띄운 다음, 데이터베이스 덤프를 불러오고, hosts 파일 항목을 그쪽으로 지정한 뒤 로그인해 보십시오. 전체 과정의 시간을 재십시오. 백업 작업이 존재한다는 사실이 아니라 바로 그 숫자가 여러분의 실제 복구 태세이며, 이 훈련을 해 본 사람은 누구나 처음에는 포함 목록에서 빠진 것을 적어도 하나는 발견했습니다.

# restic keeps absolute paths, so the stack reappears under the target.
restic restore latest --target /srv/restore
cd /srv/restore/srv/forgejo

docker compose up -d db                # empty volume: the entrypoint creates the database
docker compose exec -T db pg_restore -U forgejo -d forgejo \
  --clean --if-exists --no-owner < db.dump

docker compose up -d                   # then sign in and walk the issue tracker

업그레이드, 간단히. 이미지는 :latest 대신 메이저 버전에 고정해서, 무인 pull이 잠든 사이 여러분을 호환성이 깨지는 변경 너머로 옮겨놓지 못하게 하십시오. 메이저 버전 경계를 넘기 전에는 릴리스 노트를 읽고, 먼저 스냅샷을 찍어두며, 새 버전을 처음 시작할 때 데이터베이스 마이그레이션이 실행된다는 점을 기억하십시오 — 이는 되돌릴 수 없는 문입니다. Watchtower 방식의 자동 업데이트는 정적 사이트에는 괜찮은 발상이지만, 여러분의 모든 브랜치를 담고 있는 머신에는 나쁜 발상입니다.

끝났다고 말하기 전에 확인할 네 가지. 로그아웃한 다음 인스턴스에 계정을 만들 방법이 전혀 없는지 확인하십시오. 세션이 없는 브라우저에서, 비공개 저장소가 단순히 금지된 것이 아니라 아예 보이지 않는지 확인하십시오. 러너가 포지의 데이터 디렉터리를 읽을 수 없는지 확인하십시오. 그리고 마지막 restic 검사가 통과했는지 확인하십시오 — 조용히 실패하는 백업 작업은 필요해지는 그날까지 누구에게나 하나쯤 있는 법입니다.

정직한 부분 · 이것이 해주지 않는 것

여러분은 한 당사자를 제거했습니다. 건물까지 없애지는 못했습니다.

포지를 셀프 호스팅하는 것은 누가 여러분의 작업물을 쥐고 있는지에 대한 진짜 변화이지, 투명인간이 되는 것은 아닙니다. 위의 모든 것을 마친 뒤에도 변하지 않는 사실이 네 가지 있으며, 이를 아는 것이 진짜 방어와 스스로에게 들려주는 이야기의 차이를 만듭니다.

디스크는 누군가의 건물 안에 있습니다. 임대한 서버는 문과 직원이 있는 데이터센터의 하드웨어에서 돌아갑니다. NordBastion은 고객의 워크로드를 들여다보지 않으며, 투명성 보고서와 워런트 캐너리는 만약 요청을 받는다면 어떤 일이 벌어질지를 설명합니다 — 하지만 정직한 진술은 구조적인 것입니다. 달라지는 것은 여러분의 저장소에 대한 제품 차원의 색인도, 커밋에 대한 분석 파이프라인도, 개정될 수 있는 개인정보 처리방침도 없다는 점입니다. 달라지지 않는 것은 그 머신이 물리적으로 존재하며 여러분의 집 안에 있지 않다는 점입니다.

인증서가 발급되는 순간 호스트네임은 공개됩니다. 공개적으로 신뢰되는 모든 인증서는 누구나 검색할 수 있는 인증서 투명성 로그에 게시되므로, git.yourproject.com은 한 번도 링크하지 않았더라도 찾아낼 수 있습니다. 이는 여러분의 코드가 유출되는 것은 아니지만, 그 프로젝트가 존재하며 대략 언제 시작되었는지가 유출되는 것입니다. 이미 공개한 이름의 서브도메인을 사용하거나, 공개 인증서를 아예 생략하고 WireGuard나 어니언 서비스로 포지에 접근하십시오.

이제 여러분은 웹 애플리케이션을 패치하는 사람입니다. 포지는 로그인 페이지이자, 신뢰할 수 없는 마크업을 위한 렌더링 파이프라인이자, SSH 서버이자, CI 엔진이며, 이 모두가 인터넷에서 접근 가능합니다. 이는 1단계의 베어 저장소보다 상당히 넓은 표면이며, 여러분이 계속 최신 상태로 유지해야 하는 것입니다. 릴리스 공지를 구독하고, 보안 릴리스는 이번 분기가 아니라 이번 주 안에 처리할 일로 다루십시오.

연합은 아직 여기 없습니다. Forgejo는 포지 간 연합(federation)에 실제로 공을 들이고 있고 그 진행 상황도 공개하고 있지만, 오늘 기준으로는 공유 플랫폼에서처럼 자신의 인스턴스에서 다른 누군가의 인스턴스로 풀 리퀘스트를 열 수는 없습니다. 실무적으로 이는 외부 기여자가 여전히 여러분의 포지에 계정을 가지고 있어야 하거나, 이메일로 패치를 받거나, 지나가며 기여하는 사람들을 위해 공개 미러를 유지해야 한다는 뜻입니다. 셋 중 여러분의 프로젝트에 맞는 방식을 미리 계획해 두고, 나중에 놀라지 마십시오.

여러분이 실제로 얻은 것을 정확히 말하면 이렇습니다. 무엇을, 누구와, 언제 만들었는지에 대한 날짜 기록이, 그것을 분석하는 것이 사업 모델의 일부인 회사의 손을 더 이상 거치지 않게 되었다는 것입니다. 이는 좁은 주장이지만, 진짜인 주장이기도 합니다. 그리고 이는 오직 6단계까지 마쳤을 때만 그 값어치를 합니다 — 검증된 복원 없이 셀프 호스팅한 포지는 프라이버시의 개선이 아니라, 브랜딩만 더 나은 단일 장애점일 뿐입니다.

사이징 · 무엇을 주문할까

포지는 작습니다. CI는 그렇지 않습니다.

티어 월별 리소스 여유 있게 돌아가는 것
Sentinel$3.902 vCPU · 4 GB · 120 GB1인 또는 소규모 팀을 위한 Forgejo + PostgreSQL. 올바른 시작점입니다.
두 번째 Sentinel$3.90러너, 독립된 서버 위에서커피 한 잔 값으로 얻는, 실제로 중요한 격리
Garrison$7.904 vCPU · 8 GB · 240 GB10인 규모 포지, 컨테이너 레지스트리, 아티팩트
Ravelin$16.908 vCPU · 16 GB · 480 GB바쁜 CI, 대용량 저장소, 여러 개의 병렬 작업

이 표의 그 무엇도 Git을 기준으로 산정되지 않았습니다. Forgejo와 PostgreSQL은 함께 유휴 상태에서 수백 메가바이트만 사용합니다. 서버를 채우는 것은 CI — 컨테이너 레이어, 빌드 캐시, 아티팩트 — 와 소스 코드가 아닌 저장소 콘텐츠입니다. 디스크를 가장 먼저, 코어를 그다음으로, 메모리를 마지막으로 산정하십시오. 여기서는 큰 서버 하나보다 작은 서버 두 대가 낫습니다. 두 번째 서버는 용량이 아니라 보안 경계이기 때문입니다.

현장 노트 · 여섯 가지 함정

이것이 잘못될 수 있는 여섯 가지 방식. 그중 둘은 돈이 듭니다.

함정 01 · 시끄러움

개방된 가입, 그리고 낯선 사람들로 가득 찬 포지

인증서가 투명성 로그에 나타나면 스캐너가 며칠 안에 뒤따르고, 가입이 열려 있는 인스턴스는 스팸 계정과 스팸 저장소로 가득 찹니다. 첫 사고가 난 뒤가 아니라 첫 부팅 전에 DISABLE_REGISTRATION을 설정하십시오.

함정 02 · 비용이 큼

남을 위해 암호화폐를 채굴하는 러너

어떤 풀 리퀘스트의 워크플로든 실행하는 CI는 그것을 발견한 누구에게나 무료 컴퓨팅입니다. 처음 기여하는 사람에게는 승인을 요구하고, 러너는 독립된 서버에 두고, 이따금 작업 소요 시간을 살펴보십시오.

함정 03 · 조용함

잘못된 ROOT_URL, 그리고 어중간하게 작동하는 모든 것

클론 URL이 잘못된 호스트를 가리키고, 웹훅이 잘못된 주소로 발신되고, OAuth 콜백이 실패하고, 아바타가 깨집니다. 인터페이스는 멀쩡해 보이므로 아무도 한 줄을 의심하지 않습니다. DOMAIN, ROOT_URL, SSH_DOMAIN, SSH_PORT를 함께 설정하고 UI에서 클론 URL을 확인하십시오.

함정 04 · 헷갈림

두 개의 SSH 서버, 하나의 포트

컨테이너는 22번을 원하는데 호스트가 이미 그것을 사용하고 있어서, 매핑이 조용히 예상치 못한 곳으로 흘러가고 git push는 비밀번호를 요구합니다. 관리용 SSH를 먼저 옮기고, 컨테이너는 전용 포트로 공개하고, ssh -T로 확인하십시오.

함정 05 · 치명적

한 번도 복원해 본 적 없는 덤프

내장 아카이브는 검증된 복구 경로가 아니라 지원용 산출물일 뿐이며, 그 SQL은 재가져오기에 알려진 문제가 있습니다. pg_dump로 데이터베이스를 덤프하고, 데이터 디렉터리는 파일 그대로 백업하며, 전체를 임시 서버에 한 번은 복원해 보십시오.

함정 06 · 느림

디스크를 채우는 것은 코드가 아니라 빌드 레이어

소스 코드는 작지만, 컨테이너 레이어와 아티팩트, CI 캐시는 그렇지 않습니다. 열정적인 파이프라인이 붙은 120GB 서버는 몇 주 만에 가득 찹니다. 타이머로 이미지를 정리하고, 아티팩트 보관 기간에 상한을 두고, CPU가 아니라 디스크를 지켜보십시오.

FAQ · 프라이빗 Git 호스팅

질문들, 답변됨.

이것을 구축하는 동안 떠오르는 열한 가지 질문 — 애초에 구축해야 할지를 결정짓는 두 가지부터 시작합니다.

Forgejo와 Gitea 중 실제로 무엇을 설치해야 할까?

대다수에게는 Forgejo이고, 그 이유는 기능이 아니라 거버넌스에 있습니다. 두 프로젝트는 조상이 같아서 사용감도 여전히 거의 동일합니다. 같은 Go 바이너리, 같은 경량 구조, 같은 웹 인터페이스, 비슷한 수준의 CI. 다른 것은 누가 그것을 조종하느냐입니다. Forgejo는 베를린에 등록된 비영리 협회인 Codeberg e.V.가 소유하고 있으며 2024년 초 하드 포크가 되었습니다. 모든 것을 자유 소프트웨어로 공개하고, 기여자에게 저작권 양도를 요구하지 않습니다. Gitea는 상표와 도메인을 소유한 영리 기업이 개발하며, 기여자에게 저작권 양도를 요구하고, 무료 기반 위에 유료 기능을 얹은 오픈 코어 모델로 운영됩니다. 이 두 사실 중 어느 것도 소프트웨어로서의 Gitea를 훼손하지는 않습니다. 하지만 저장소를 플랫폼에서 옮기는 것의 핵심은 어느 기업의 로드맵에 더 이상 의존하지 않기 위해서이므로, 관리 주체가 비영리 단체인 포크를 고르는 것이 여러분이 여기 있는 이유에 부합하는 선택입니다. 나중에 전환할 경우를 위한 실용적인 참고 사항 하나: Forgejo는 기존 Gitea 데이터 디렉터리 위에 설치할 수 있지만, 그 반대 방향은 지원되지 않습니다.

애초에 포지가 필요하긴 할까? SSH를 통한 베어 저장소로는 부족할까?

노트북 한 대와 좋은 커밋 메시지를 쓰는 습관을 가진 한 사람에게는 베어 저장소만으로도 충분하며, 이는 이 가이드에서 가장 견고한 선택이기도 합니다. 서버에서 git init --bare를 실행하고 평범한 SSH 계정을 두면, 패치할 웹 애플리케이션도, 백업할 데이터베이스도, sshd 너머의 공격 표면도 없습니다. 이 구성은 그 무엇도 결코 단종되지 않습니다. 두 번째 사람이 관여하는 순간, 또는 베어 저장소가 전혀 알지 못하는 네 가지 — 브라우저로 볼 수 있는 이력, 리뷰가 가능한 풀 리퀘스트, 코드 옆에 있는 이슈 트래커, 저장소별 접근 제어 — 중 하나라도 원하는 순간 포지가 필요해집니다. 이는 작은 발전이 아니라 저장과 협업의 차이입니다. 여러분이 해결하려는 문제가 무엇인지 스스로 솔직해지십시오. 포지는 이제 여러분이 운영해야 하는 서비스이고, 베어 저장소는 그냥 디렉터리이기 때문입니다.

이슈와 풀 리퀘스트를 포함해 GitHub 저장소를 마이그레이션할 수 있을까?

가능하며, 셀프 호스팅에서 손꼽히게 괜찮은 마이그레이션 사례이기도 합니다. Forgejo에 내장된 마이그레이션 도구는 저장소 URL과 개인 액세스 토큰만으로 코드보다 훨씬 많은 것을 가져옵니다: 브랜치와 태그, 댓글이 달린 이슈, 풀 리퀘스트, 레이블, 마일스톤, 릴리스까지 말입니다. 읽기 범위의 토큰을 발급하고, 먼저 저장소 하나로 결과물의 모습을 확인한 다음 나머지를 진행하십시오. 예상해야 할 세 가지가 있습니다. 크거나 수가 많은 마이그레이션은 원본 플랫폼의 API 속도 제한에 걸려 단순히 더 오래 걸립니다 — 시작해 놓고 그냥 두십시오. 이슈의 첨부 파일은 네트워크를 통해 가져오므로, 아웃바운드가 제한된 방화벽 뒤의 인스턴스에서는 이미지가 깨진 이슈가 생길 수 있습니다. 그리고 두 데이터 모델이 동일하지 않기 때문에 리뷰 스레드가 항상 원래 있던 자리에 정확히 놓이지는 않습니다. 원본 저장소를 그날 오후에 바로 삭제하기보다는 몇 주간 읽기 전용으로 유지해 두는 계획을 세우십시오.

셀프 호스팅한 포지는 정말로 더 프라이빗한 걸까, 아니면 그저 다를 뿐일까?

한 가지 구체적이고 중요한 지점에서 진짜로 다릅니다. 그 색인을 쥐고 있는 것이 오직 여러분뿐이라는 점입니다. 플랫폼에서는 비공개 저장소가 다른 사용자에게는 비공개이지만, 그것을 저장하고 스캔하고 의존성 그래프를 계산하며 자국 관할권의 법원에 의해 제출을 강요받을 수 있는 플랫폼 자신에게는 비공개가 아닙니다. 그 색인은 코드가 시시하더라도 유별나게 많은 것을 드러냅니다. 커밋 타임스탬프는 여러분의 근무 시간을, 공동 작성자는 여러분의 팀을, 브랜치 이름은 아직 공개되지 않은 제품을, 그리고 첫 커밋은 발표되지 않은 프로젝트의 시작일을 알려줍니다. 이를 여러분 자신의 서버로 옮기면 그 그림에서 플랫폼이 완전히 사라집니다. 하지만 그것이 하지 못하는 일도 있습니다. 바로 그 머신을 사라지게 만드는 것입니다. 여러분은 여전히 데이터센터의 하드웨어를 임대하고 있고, 그 디스크는 여전히 건물 소유자에게 물리적으로 접근 가능하며, 여러분이 고른 호스트네임은 여전히 인증서 투명성 로그에 남아 있습니다. 정직하게 요약하면: 여러분은 계약상의 프라이버시 정책을 가진 회사를, 오직 여러분의 설정만이 정책인 서버로 대체한 것이며, 그것도 스스로 신중하게 고른 관할권에서 그렇게 한 것입니다 — 그리고 그 비용을 지불하는 계정은 여러분의 이름을 달고 있을 필요가 없습니다.

VPS는 얼마나 커야 하나요?

GitLab 문서가 모두에게 기대하도록 길들여 놓은 것보다 훨씬 작습니다. Forgejo와 Gitea는 유휴 상태에서 수백 메가바이트의 RAM만 쓰는 단일 Go 바이너리입니다. 여기에 PostgreSQL과 리버스 프록시를 더해도, 1인 개발자의 포지는 여유 있게 Sentinel — 2 vCPU, 4GB, 120GB NVMe, 월 $3.90 — 안에 들어갑니다. 부담은 다른 세 곳에서 오며, 각각을 의도적으로 산정해 볼 가치가 있습니다. CI가 가장 큰 요인입니다. 컨테이너를 빌드하는 러너는 주어진 코어를 전부 쓰고 디스크를 레이어로 채웁니다. 저장소 크기가 두 번째입니다. 게임 에셋, 노트북 파일, 번들된 의존성은 소스 코드보다 훨씬 빠르게 커집니다. 아티팩트와 컨테이너 레지스트리가 세 번째입니다. 실제로 잘 통하는 원칙 하나 — 포지는 Sentinel 하나에, 러너는 별도의 두 번째 Sentinel에 두고, CPU가 아니라 디스크가 신호를 보낼 때만 티어를 올리십시오.

Forgejo Actions가 정말로 내 GitHub Actions 워크플로와 호환될까?

대부분의 단순한 워크플로가 그대로 실행될 만큼은 호환되지만, 복잡한 파이프라인을 읽어 보지도 않고 옮겨도 될 만큼 호환되지는 않습니다. 워크플로 파일 형식은 동일하며, .forgejo/workflows/에 위치합니다(.github/workflows/도 읽습니다). 체크아웃, 언어 설정, 캐시, 매트릭스 빌드 같은 공통 구성 요소는 러너가 동일한 방식으로 컨테이너에서 실행하기 때문에 예상대로 동작합니다. 깨지는 것은 Actions라는 형식이 아니라 GitHub라는 플랫폼 자체를 전제로 하는 것들입니다. github.com URL에 고정된 마켓플레이스 액션(여러분의 인스턴스가 이를 가져올 수 있도록 허용해야 함), 릴리스나 PR 메타데이터를 위해 GitHub REST API를 호출하는 액션, 매우 특정한 도구 모음이 미리 설치된 GitHub 호스팅 러너 이미지, 그리고 OIDC 기반 클라우드 인증이 그렇습니다. 효과가 있는 마이그레이션 패턴은 다음과 같습니다. 빌드와 테스트만 하는 워크플로로 시작해서 초록불이 뜨게 만든 다음, 배포 단계를 하나씩 다시 추가하십시오.

VPS가 죽으면 내 코드는 어떻게 될까?

코드 자체는 괜찮은 부분입니다. 이것이 다른 거의 모든 셀프 호스팅 서비스에 비해 Git이 가진 조용한 강점입니다. 모든 클론은 이력의 완전한 사본이므로, 개발자 한 명이라도 최신 작업 사본을 가지고 있는 한 저장소는 사라지지 않습니다. 서버와 함께 사라지는 것은 코드를 둘러싼 나머지 전부입니다 — 이슈, 풀 리퀘스트 논의, 리뷰 댓글, 릴리스, 위키, 사용자, 접근 규칙, CI 이력. 이 자료는 오직 데이터베이스 안에만 있고, 아무도 그 사본을 갖고 있지 않으며, 대개 몇 년에 걸쳐 쌓인 부분이기도 합니다. 그러니 이 둘을 다르게 다루십시오. 코드는 두 번째 위치로 향하는 푸시 미러를 설정해 두면 별다른 수고 없이 지속적이고 자동적인 오프사이트 보호를 얻습니다. 메타데이터는 진짜 백업이 필요합니다 — PostgreSQL 덤프와 데이터 디렉터리를 암호화해서, 타이머에 맞춰 다른 관할권의 머신으로 보내는 것입니다. 6단계에서 이 둘을 모두 다루며, 마지막의 복원 훈련이야말로 이를 의도에서 보증으로 바꿔주는 부분입니다.

완전히 떠나는 대신 GitHub를 미러로 유지할 수 있을까?

가능하며, 많은 프로젝트에서 이는 타협이 아니라 정답입니다. Forgejo는 푸시 미러를 지원하므로, 여러분의 인스턴스가 팀이 작업하는 오리진이 되고 모든 푸시는 GitHub, Codeberg, 혹은 두 번째 Forgejo 위의 저장소로 자동 복제됩니다. 공개 가시성, 지나가다 기여하는 사람들, GitHub URL을 기대하는 패키지 레지스트리, 오래된 README 속 이슈 트래커 링크는 모두 계속 작동하는 한편, 진실의 원천 — 그리고 비공개 저장소, CI 로그, 공개하지 않은 것들의 이슈 이력 — 은 여러분의 서버에 그대로 남습니다. 반대 방향으로도 작동합니다. 풀 미러는 여러분이 의존하는 업스트림 프로젝트의 로컬 사본을 항상 최신 상태로 유지해 주며, 내일 그것이 삭제되면 곤란해질 만한 것이라면 무엇이든 이렇게 해 둘 가치가 있습니다. 신중해야 할 단 한 가지는 무엇을 미러링하고 있느냐입니다. 삭제하려던 비밀 정보가 이력에 남아 있는 저장소를 푸시 미러로 두면, 그 비밀은 즉시 그리고 자동으로 다시 게시됩니다.

계약직에게 나머지는 빼고 저장소 하나에만 접근 권한을 주려면?

접근이 지속되어야 할 기간에 맞는 계층을 사용하십시오. 읽고, 커밋하고, 풀 리퀘스트를 여는 사람에게는 사용자 계정을 만들고, 조용히 더 많은 권한을 부여하는 조직 팀에 넣는 대신 그가 정확히 필요로 하는 저장소에만 읽기, 쓰기, 관리자 중 명시적인 권한 수준으로 협업자로 추가하십시오. 가져오기만 하면 되는 자동화 시스템 — 배포 대상, 빌드 서버, 미러 — 에는 사용자 계정 대신 읽기 전용 배포 키를 사용하십시오. 이는 저장소 하나로 범위가 제한되고, 그 외에는 아무 권한도 주지 않으며, 회수도 한 줄 삭제로 끝납니다. API와 통신하는 스크립트에는 비밀번호를 넘기는 대신 범위가 제한된 액세스 토큰을 발급하십시오. 두 가지 습관이 이 전체를 지탱합니다. 존재하는 계정이 오직 여러분이 만든 것뿐이도록 가입을 계속 차단해 두는 것, 그리고 임시 접근 권한을 부여할 때 달력에 날짜를 적어 두는 것입니다. 어떤 포지도 계약이 끝났다는 사실을 알아서 알려주지는 않기 때문입니다.

포지에 도메인이 꼭 필요할까, 그리고 그것이 내 신원을 노출할까?

웹 인터페이스를 위한 호스트네임은 필요합니다. TLS 인증서는 이름에 대해 발급되며, 여기서 브라우저가 IP 주소를 신뢰해서는 안 되기 때문입니다. 하지만 그 이름이 여러분에게까지 추적 가능할 필요는 없습니다. 신중하게 고려해야 할 세 가지가 있습니다. 다른 곳에서는 신경 써서 지켰던 신원 정보를 요구하지 않는 곳에서 도메인을 등록하고, WHOIS 프라이버시를 켜 두십시오 — 등록기관은 호스트와는 별개의 신뢰 판단이며, 사람들이 흔히 잊어버리는 부분입니다. 인증서가 발급되는 순간 호스트네임이 공개된다고 예상하십시오. 공개적으로 신뢰되는 모든 인증서는 누구나 검색할 수 있는 인증서 투명성 로그에 게시되기 때문입니다. 이미 사용 중인 도메인의 와일드카드를 쓰면 새로 검색 가능한 이름이 추가되는 것을 피할 수 있습니다. 그리고 공개된 이름을 아예 두고 싶지 않다면, 실제로 대안이 있습니다. 포지를 Tor 어니언 서비스에 두거나 WireGuard로만 접근 가능하게 하고, 공개 인증서를 아예 생략하는 것입니다. 다만 어디서든 브라우저를 열면 되는 편의는 함께 포기해야 합니다.

NordBastion은 제가 VPS에서 Git 포지와 CI 러너를 운영해도 개의치 않을까?

전혀 아닙니다 — 포지와 그 러너는 평범한 워크로드이며, 저희 고객이 구축하는 것 중에서도 꽤 흔한 축에 속합니다. 이용 약관(acceptable-use policy)은 소스 코드를 호스팅하거나 빌드하는 것에 대해 아무런 언급도 하지 않습니다. 정책이라기보다는 운영상의 참고 사항 두 가지가 있습니다. 첫째, 낯선 사람의 워크플로를 받아들이는 CI 러너는 그것을 발견한 누구에게나 무료 컴퓨팅 서비스가 됩니다. 흔한 악용 사례는 풀 리퀘스트 안에 심어진 암호화폐 채굴이며, 열려 있으면 며칠 안에 발견됩니다. 가입을 차단해 두고, 아직 협업자가 아닌 사람의 워크플로는 실행 전에 승인을 요구하십시오. 둘째, 빌드가 실행할 때마다 대용량 컨테이너 이미지를 가져온다면 그것은 정상적인 트래픽이고 요금에 포함되어 있습니다 — 다만 파이프라인을 더 빠르게 해 주고, 의존하고 있는 업스트림 레지스트리에 대한 예의이기도 하니 어차피 로컬에 캐싱해 둘 가치는 있습니다.

서버를 확보하십시오

여러분 이름이 아닌 계정 위의, 여러분만의 포지. KYC 없음, 암호화폐 결제.

Sentinel — Helsinki, Stockholm, Oslo, Reykjavík 중 어디서든 월 $3.90에 2 vCPU와 4GB. Forgejo, PostgreSQL, 리버스 프록시를 돌리고도 옮기려고 벼르던 저장소들을 위한 여유가 남습니다. 러너용으로 하나 더 추가하십시오. 가입 시 이메일도, 신분증도 필요 없고, 여러분이 푸시하는 무엇에 대해서도 제품 차원의 색인이 남지 않습니다.

마지막 검토일 · 2026-10-12 · 출처 · Forgejo 관리자 문서(설치, Actions, 명령줄), Forgejo와 Gitea 비교, Codeberg e.V. 거버넌스 페이지, PostgreSQL 및 Caddy 문서, git-config(1) · 주기 · 연간

북유럽에서 구축한 프라이빗 호스팅.