포스트

[이제와서 시작하는 GitHub 마스터하기 - 고급편 #6] GitHub Packages: 패키지 레지스트리 활용하기

[이제와서 시작하는 GitHub 마스터하기 - 고급편 #6] GitHub Packages: 패키지 레지스트리 활용하기

들어가며

“이제와서 시작하는 GitHub 마스터하기” 시리즈의 열일곱 번째 시간입니다. 이번에는 GitHub Packages를 활용하여 다양한 패키지를 호스팅하고 관리하는 방법을 알아보겠습니다. GitHub Packages는 소스 코드와 패키지를 한 곳에서 관리할 수 있게 해주는 통합 패키지 레지스트리입니다.

1. GitHub Packages 개요

지원하는 패키지 유형

1
2
3
4
5
6
7
8
9
10
11
12
13
Container images:
  - Docker
  - OCI (Open Container Initiative)

Language packages:
  - npm (JavaScript/TypeScript)
  - RubyGems (Ruby)
  - Maven/Gradle (Java/Kotlin)
  - NuGet (.NET)
  - Cargo (Rust)

General packages:
  - Generic packages (any file type)

주요 특징

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
통합성:
  - GitHub 저장소와 직접 연결
  - Actions와 완벽한 통합
  - 권한 관리 일원화

보안:
  - 프라이빗 패키지 지원
  - 세분화된 접근 제어
  - 취약점 스캔 통합

제한사항:
  Public repositories:
    - 무료 무제한
  Private repositories:
    - Free: 500MB 저장소, 1GB/월 전송
    - Pro: 2GB 저장소, 10GB/월 전송
    - Team: 2GB 저장소, 10GB/월 전송
    - Enterprise: 50GB 저장소, 100GB/월 전송

2. npm 패키지 배포

패키지 설정

package.json:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
{
  "name": "@username/my-awesome-package",
  "version": "1.0.0",
  "description": "An awesome package hosted on GitHub Packages",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "files": [
    "dist",
    "README.md",
    "LICENSE"
  ],
  "scripts": {
    "build": "tsc",
    "prepare": "npm run build",
    "test": "jest",
    "prepublishOnly": "npm test && npm run build"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/username/my-awesome-package.git"
  },
  "publishConfig": {
    "registry": "https://npm.pkg.github.com"
  },
  "keywords": ["awesome", "package"],
  "author": "Your Name",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/username/my-awesome-package/issues"
  },
  "homepage": "https://github.com/username/my-awesome-package#readme"
}

npm 설정

.npmrc:

1
2
@username:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}

GitHub Actions로 자동 배포

.github/workflows/npm-publish.yml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
name: Publish npm Package

on:
  release:
    types: [created]
  workflow_dispatch:
    inputs:
      version:
        description: 'Version to publish'
        required: true
        default: 'patch'
        type: choice
        options:
          - patch
          - minor
          - major

permissions:
  contents: read
  packages: write

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [16.x, 18.x, 20.x]
    steps:
      - uses: actions/checkout@v4
      
      - name: Use Node.js $
        uses: actions/setup-node@v4
        with:
          node-version: $
          cache: 'npm'
          
      - name: Install dependencies
        run: npm ci
        
      - name: Run tests
        run: npm test
        
      - name: Lint
        run: npm run lint
        
      - name: Type check
        run: npm run type-check

  publish:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          token: $
          
      - uses: actions/setup-node@v4
        with:
          node-version: '18.x'
          registry-url: 'https://npm.pkg.github.com'
          scope: '@username'
          
      - name: Install dependencies
        run: npm ci
        
      - name: Configure Git
        run: |
          git config user.name "GitHub Actions"
          git config user.email "actions@github.com"
          
      - name: Bump version
        if: github.event_name == 'workflow_dispatch'
        run: |
          npm version $ --no-git-tag-version
          VERSION=$(node -p "require('./package.json').version")
          git add package.json package-lock.json
          git commit -m "chore: bump version to v$VERSION"
          git tag "v$VERSION"
          git push origin main --tags
          
      - name: Build package
        run: npm run build
        
      - name: Publish to GitHub Packages
        run: npm publish
        env:
          NODE_AUTH_TOKEN: $
          
      - name: Publish to npm (optional)
        run: |
          npm config set //registry.npmjs.org/:_authToken=$NPM_TOKEN
          npm config set registry https://registry.npmjs.org
          npm publish --access public
        env:
          NPM_TOKEN: $
        if: secrets.NPM_TOKEN != ''

패키지 사용하기

1
2
3
4
5
6
7
8
# .npmrc 설정
echo "@username:registry=https://npm.pkg.github.com" >> .npmrc

# 인증 (Personal Access Token 필요)
npm login --scope=@username --registry=https://npm.pkg.github.com

# 패키지 설치
npm install @username/my-awesome-package

3. Docker 이미지 배포

Dockerfile 준비

Dockerfile:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# Multi-stage build
FROM node:18-alpine AS builder

WORKDIR /app

# 의존성 캐시
COPY package*.json ./
RUN npm ci --only=production

# 앱 복사 및 빌드
COPY . .
RUN npm run build

# Production image
FROM node:18-alpine

# 메타데이터
LABEL org.opencontainers.image.source="https://github.com/username/my-app"
LABEL org.opencontainers.image.description="My awesome application"
LABEL org.opencontainers.image.licenses="MIT"

WORKDIR /app

# 프로덕션 의존성만 복사
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./

EXPOSE 3000

USER node

CMD ["node", "dist/server.js"]

GitHub Actions로 Docker 이미지 배포

.github/workflows/docker-publish.yml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
name: Build and Push Docker Image

on:
  push:
    branches: [ main ]
    tags: [ 'v*' ]
  pull_request:
    branches: [ main ]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: $

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
      
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
        
      - name: Log in to Container Registry
        if: github.event_name != 'pull_request'
        uses: docker/login-action@v3
        with:
          registry: $
          username: $
          password: $
          
      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: $/$
          tags: |
            type=ref,event=branch
            type=ref,event=pr
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=semver,pattern={{major}}
            type=sha,prefix={{branch}}-
            type=raw,value=latest,enable={{is_default_branch}}
            
      - name: Build and push Docker image
        uses: docker/build-push-action@v5
        with:
          context: .
          platforms: linux/amd64,linux/arm64
          push: ${{ github.event_name != 'pull_request' }}
          tags: $
          labels: $
          cache-from: type=gha
          cache-to: type=gha,mode=max
          build-args: |
            VERSION=$
            COMMIT_SHA=$
            BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')
          
      - name: Generate SBOM
        uses: anchore/sbom-action@v0
        with:
          image: $/$:$
          format: spdx-json
          output-file: sbom.spdx.json
          
      - name: Sign container image
        if: github.event_name != 'pull_request'
        env:
          COSIGN_PASSWORD: $
        run: |
          cosign sign --key cosign.key \
            $/$@$

Docker 이미지 사용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 로그인
docker login ghcr.io -u USERNAME -p TOKEN

# 이미지 풀
docker pull ghcr.io/username/my-app:latest

# 실행
docker run -p 3000:3000 ghcr.io/username/my-app:latest

# docker-compose.yml
version: '3.8'
services:
  app:
    image: ghcr.io/username/my-app:latest
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production

4. Maven 패키지 배포

pom.xml 설정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    
    <groupId>com.github.username</groupId>
    <artifactId>my-awesome-library</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>
    
    <name>My Awesome Library</name>
    <description>An awesome Java library</description>
    <url>https://github.com/username/my-awesome-library</url>
    
    <licenses>
        <license>
            <name>MIT License</name>
            <url>https://opensource.org/licenses/MIT</url>
        </license>
    </licenses>
    
    <developers>
        <developer>
            <name>Your Name</name>
            <email>you@example.com</email>
        </developer>
    </developers>
    
    <scm>
        <connection>scm:git:git://github.com/username/my-awesome-library.git</connection>
        <developerConnection>scm:git:ssh://github.com:username/my-awesome-library.git</developerConnection>
        <url>https://github.com/username/my-awesome-library</url>
    </scm>
    
    <distributionManagement>
        <repository>
            <id>github</id>
            <name>GitHub Packages</name>
            <url>https://maven.pkg.github.com/username/my-awesome-library</url>
        </repository>
    </distributionManagement>
    
    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-source-plugin</artifactId>
                <version>3.2.1</version>
                <executions>
                    <execution>
                        <id>attach-sources</id>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-javadoc-plugin</artifactId>
                <version>3.4.0</version>
                <executions>
                    <execution>
                        <id>attach-javadocs</id>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

Maven settings.xml

~/.m2/settings.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
          http://maven.apache.org/xsd/settings-1.0.0.xsd">
    
    <activeProfiles>
        <activeProfile>github</activeProfile>
    </activeProfiles>
    
    <profiles>
        <profile>
            <id>github</id>
            <repositories>
                <repository>
                    <id>github</id>
                    <url>https://maven.pkg.github.com/username/*</url>
                </repository>
            </repositories>
        </profile>
    </profiles>
    
    <servers>
        <server>
            <id>github</id>
            <username>USERNAME</username>
            <password>${env.GITHUB_TOKEN}</password>
        </server>
    </servers>
</settings>

GitHub Actions로 Maven 패키지 배포

.github/workflows/maven-publish.yml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
name: Publish Maven Package

on:
  release:
    types: [created]

jobs:
  publish:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
      
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up JDK 11
        uses: actions/setup-java@v3
        with:
          java-version: '11'
          distribution: 'temurin'
          cache: maven
          
      - name: Configure Maven
        uses: s4u/maven-settings-action@v2.8.0
        with:
          servers: |
            [{
              "id": "github",
              "username": "$",
              "password": "$"
            }]
            
      - name: Build with Maven
        run: mvn clean compile
        
      - name: Run tests
        run: mvn test
        
      - name: Publish to GitHub Packages
        run: mvn deploy
        env:
          GITHUB_TOKEN: $

5. NuGet 패키지 배포

.csproj 설정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <PackageId>MyAwesomeLibrary</PackageId>
    <Version>1.0.0</Version>
    <Authors>Your Name</Authors>
    <Company>YourCompany</Company>
    <PackageDescription>An awesome .NET library</PackageDescription>
    <RepositoryUrl>https://github.com/username/my-awesome-library</RepositoryUrl>
    <PackageLicenseExpression>MIT</PackageLicenseExpression>
    <PackageProjectUrl>https://github.com/username/my-awesome-library</PackageProjectUrl>
    <PackageTags>awesome;library;dotnet</PackageTags>
    <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
    <IncludeSymbols>true</IncludeSymbols>
    <SymbolPackageFormat>snupkg</SymbolPackageFormat>
  </PropertyGroup>
</Project>

GitHub Actions로 NuGet 패키지 배포

.github/workflows/nuget-publish.yml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
name: Publish NuGet Package

on:
  release:
    types: [published]

env:
  DOTNET_VERSION: '6.0.x'

jobs:
  publish:
    runs-on: ubuntu-latest
    permissions:
      packages: write
      contents: read
      
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup .NET
        uses: actions/setup-dotnet@v3
        with:
          dotnet-version: $
          
      - name: Restore dependencies
        run: dotnet restore
        
      - name: Build
        run: dotnet build --configuration Release --no-restore
        
      - name: Test
        run: dotnet test --no-restore --verbosity normal
        
      - name: Pack
        run: dotnet pack --configuration Release --no-build --output ./artifacts
        
      - name: Push to GitHub Packages
        run: |
          dotnet nuget add source --username $ \
            --password $ \
            --store-password-in-clear-text \
            --name github "https://nuget.pkg.github.com/username/index.json"
          
          dotnet nuget push "./artifacts/*.nupkg" \
            --api-key $ \
            --source "github" \
            --skip-duplicate

6. RubyGems 패키지 배포

gemspec 파일

my-awesome-gem.gemspec:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Gem::Specification.new do |spec|
  spec.name          = "my-awesome-gem"
  spec.version       = "1.0.0"
  spec.authors       = ["Your Name"]
  spec.email         = ["you@example.com"]
  
  spec.summary       = %q{An awesome Ruby gem}
  spec.description   = %q{A longer description of this awesome Ruby gem}
  spec.homepage      = "https://github.com/username/my-awesome-gem"
  spec.license       = "MIT"
  
  spec.metadata["homepage_uri"] = spec.homepage
  spec.metadata["source_code_uri"] = spec.homepage
  spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
  
  spec.files         = Dir.chdir(File.expand_path('..', __FILE__)) do
    `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
  end
  
  spec.bindir        = "exe"
  spec.executables   = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
  spec.require_paths = ["lib"]
  
  spec.required_ruby_version = ">= 2.6.0"
  
  spec.add_development_dependency "bundler", "~> 2.0"
  spec.add_development_dependency "rake", "~> 13.0"
  spec.add_development_dependency "rspec", "~> 3.0"
end

GitHub Actions로 RubyGems 배포

.github/workflows/gem-publish.yml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
name: Publish Ruby Gem

on:
  push:
    tags:
      - v*

jobs:
  publish:
    runs-on: ubuntu-latest
    permissions:
      packages: write
      contents: read
      
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: '3.0'
          bundler-cache: true
          
      - name: Run tests
        run: bundle exec rake spec
        
      - name: Build gem
        run: gem build *.gemspec
        
      - name: Push to GitHub Packages
        run: |
          mkdir -p ~/.gem
          echo "---" > ~/.gem/credentials
          echo ":github: Bearer $" >> ~/.gem/credentials
          chmod 0600 ~/.gem/credentials
          
          gem push --key github \
            --host https://rubygems.pkg.github.com/username \
            *.gem

7. 멀티 패키지 관리

Monorepo 구조

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
my-monorepo/
├── packages/
│   ├── core/
│   │   ├── package.json
│   │   └── src/
│   ├── ui/
│   │   ├── package.json
│   │   └── src/
│   └── utils/
│       ├── package.json
│       └── src/
├── docker/
│   ├── app/
│   │   └── Dockerfile
│   └── worker/
│       └── Dockerfile
├── lerna.json
├── package.json
└── .github/
    └── workflows/
        └── publish-all.yml

Lerna를 사용한 멀티 패키지 배포

lerna.json:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
  "version": "independent",
  "npmClient": "npm",
  "command": {
    "publish": {
      "registry": "https://npm.pkg.github.com",
      "conventionalCommits": true,
      "message": "chore(release): publish",
      "createRelease": "github"
    },
    "version": {
      "allowBranch": ["main", "release/*"],
      "conventionalCommits": true
    }
  },
  "packages": ["packages/*"]
}

.github/workflows/publish-all.yml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
name: Publish All Packages

on:
  push:
    branches: [ main ]
  workflow_dispatch:

jobs:
  publish:
    runs-on: ubuntu-latest
    permissions:
      contents: write
      packages: write
      
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
          token: $
          
      - uses: actions/setup-node@v4
        with:
          node-version: '18'
          registry-url: 'https://npm.pkg.github.com'
          
      - name: Configure Git
        run: |
          git config user.name "GitHub Actions"
          git config user.email "actions@github.com"
          
      - name: Install dependencies
        run: npm ci
        
      - name: Build all packages
        run: npm run build
        
      - name: Run tests
        run: npm test
        
      - name: Version and publish
        run: |
          npm run lerna version -- --yes
          npm run lerna publish from-git -- --yes
        env:
          NODE_AUTH_TOKEN: $
          GH_TOKEN: $

8. 패키지 버전 관리

Semantic Release 설정

.releaserc.json:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
{
  "branches": ["main"],
  "plugins": [
    "@semantic-release/commit-analyzer",
    "@semantic-release/release-notes-generator",
    "@semantic-release/changelog",
    "@semantic-release/npm",
    [
      "@semantic-release/github",
      {
        "assets": [
          {
            "path": "dist/**/*.js",
            "label": "JavaScript distribution"
          },
          {
            "path": "dist/**/*.d.ts",
            "label": "TypeScript definitions"
          }
        ]
      }
    ],
    [
      "@semantic-release/git",
      {
        "assets": ["CHANGELOG.md", "package.json", "package-lock.json"],
        "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
      }
    ]
  ]
}

자동 버전 관리 워크플로우

.github/workflows/semantic-release.yml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
name: Semantic Release

on:
  push:
    branches: [ main ]

permissions:
  contents: write
  packages: write
  issues: write
  pull-requests: write

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
          persist-credentials: false
          
      - uses: actions/setup-node@v4
        with:
          node-version: '18'
          registry-url: 'https://npm.pkg.github.com'
          
      - name: Install dependencies
        run: npm ci
        
      - name: Build
        run: npm run build
        
      - name: Semantic Release
        env:
          GITHUB_TOKEN: $
          NODE_AUTH_TOKEN: $
        run: npx semantic-release

9. 패키지 사용 및 관리

패키지 권한 관리

1
2
3
4
5
6
7
8
9
10
# Repository Settings → Manage Access
Package permissions:
  - Read: 패키지 다운로드
  - Write: 패키지 업로드/수정
  - Admin: 패키지 삭제

Visibility:
  - Private: 조직/협업자만
  - Internal: 조직 내부
  - Public: 모든 사용자

패키지 메타데이터

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// package.json 예시
{
  "name": "@org/package",
  "version": "1.0.0",
  "description": "Package description",
  "keywords": ["github", "packages"],
  "homepage": "https://github.com/org/repo#readme",
  "bugs": {
    "url": "https://github.com/org/repo/issues"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/org/repo.git"
  },
  "funding": {
    "type": "github",
    "url": "https://github.com/sponsors/username"
  }
}

패키지 사용 통계

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 패키지 다운로드 통계 조회
const { Octokit } = require('@octokit/rest');

async function getPackageStats(owner, packageType, packageName) {
  const octokit = new Octokit({
    auth: process.env.GITHUB_TOKEN
  });
  
  try {
    // 패키지 정보 조회
    const { data: pkg } = await octokit.packages.getPackageForOrganization({
      package_type: packageType,
      package_name: packageName,
      org: owner
    });
    
    // 버전 목록 조회
    const { data: versions } = await octokit.packages.getAllPackageVersionsForPackageOwnedByOrg({
      package_type: packageType,
      package_name: packageName,
      org: owner
    });
    
    return {
      name: pkg.name,
      type: pkg.package_type,
      visibility: pkg.visibility,
      created_at: pkg.created_at,
      updated_at: pkg.updated_at,
      version_count: versions.length,
      latest_version: versions[0]?.name,
      total_downloads: versions.reduce((sum, v) => sum + (v.metadata?.download_count || 0), 0)
    };
  } catch (error) {
    console.error('Error fetching package stats:', error);
    throw error;
  }
}

10. 모범 사례

패키지 보안

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 패키지 스캔 워크플로우
name: Package Security Scan

on:
  schedule:
    - cron: '0 0 * * 0'
  workflow_dispatch:

jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - name: Scan npm packages
        run: |
          npm audit --production
          npm outdated
          
      - name: Scan Docker images
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: 'ghcr.io/$:latest'
          format: 'sarif'
          output: 'trivy-results.sarif'
          
      - name: Upload results
        uses: github/codeql-action/upload-sarif@v2
        with:
          sarif_file: 'trivy-results.sarif'

패키지 문서화

1
2
3
4
5
6
7
8
9
10
11
12
<!-- README.md -->
# Package Name

[![npm version](https://img.shields.io/npm/v/@org/package.svg)](https://github.com/org/repo/packages)
[![Docker Image Size](https://img.shields.io/docker/image-size/org/image)](https://github.com/org/repo/pkgs/container/image)
[![License](https://img.shields.io/github/license/org/repo)](LICENSE)

## Installation

### npm
```bash
npm install @org/package

Docker

1
docker pull ghcr.io/org/image:latest

Maven

1
2
3
4
5
<dependency>
  <groupId>com.github.org</groupId>
  <artifactId>package</artifactId>
  <version>1.0.0</version>
</dependency>

Usage

[사용 예제]

API Documentation

[API 문서]

Contributing

[기여 가이드]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
### 비용 최적화

```yaml
최적화 전략:
  이미지 크기 줄이기:
    - Multi-stage builds 사용
    - Alpine 기반 이미지 사용
    - 불필요한 파일 제외
    
  캐시 활용:
    - Layer 캐싱 최적화
    - GitHub Actions 캐시 사용
    
  정리 정책:
    - 오래된 버전 자동 삭제
    - 미사용 패키지 정리
    
  모니터링:
    - 사용량 추적
    - 비용 알림 설정

마무리

GitHub Packages는 코드와 패키지를 한 곳에서 관리할 수 있는 강력한 도구입니다.

핵심 포인트:

  • 다양한 패키지 형식 지원
  • GitHub Actions와 완벽한 통합
  • 세분화된 권한 관리
  • 프라이빗 패키지 지원
  • 통합된 보안 스캔

소스 코드와 패키지를 함께 관리하여 개발 워크플로우를 간소화하고 보안을 강화하세요.

다음 편에서는 Codespaces를 활용한 클라우드 개발 환경에 대해 알아보겠습니다.

📚 GitHub 마스터하기 시리즈

🌱 기초편 (입문자)

  1. GitHub 시작하기
  2. Repository 기초
  3. Git 기본 명령어
  4. Branch와 Merge
  5. Fork와 Pull Request

💼 실전편 (중급자)

  1. Issues 활용법
  2. Projects로 프로젝트 관리
  3. Code Review 잘하기
  4. GitHub Discussions
  5. Team 협업 설정
  6. GitHub Pages

🚀 고급편 (전문가)

  1. GitHub Actions 입문
  2. Actions 고급 활용
  3. Webhooks와 API
  4. GitHub Apps 개발
  5. 보안 기능
  6. [GitHub Packages] (현재 글)(/posts/github-advanced-06-github-packages/)
  7. Codespaces
  8. GitHub CLI
  9. 통계와 인사이트

🏆 심화편 (전문가+)

  1. Git Submodules & Subtree
  2. Git 내부 동작 원리
  3. 고급 브랜치 전략과 릴리스 관리
  4. GitHub GraphQL API
  5. GitHub Copilot 완벽 활용

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.