Skip to main content
DevOps dari nol · Pelajaran 24 dari 25 · 14 menit

Cara Memakai Secrets GitHub Actions: Variables, Scope, Fork

Cara memakai secrets GitHub Actions: beda secret dan variable, scope repository dan environment, masking di log, pull request dari fork, dan GITHUB_TOKEN.

Oleh Matthews WongDitinjau pada

Poin utama

Secrets GitHub Actions adalah nilai terenkripsi, seperti API key dan SSH key, yang dibaca job lewat context secrets; runner menggantinya dengan *** di log. Configuration variable menyimpan pengaturan yang tidak sensitif, dibaca lewat vars, dan tercetak apa adanya. Keduanya ada di level organization, repository, atau environment, level paling rendah yang menang, dan pull request dari fork tidak mendapat secret.

Setelah pelajaran ini, Anda bisa

  • Menentukan apakah sebuah nilai masuk ke secret atau variable, di level mana, dan menebak nilai mana yang diterima job saat satu nama ada di dua level.
  • Membaca secret dan variable di dalam job dengan aman: lewat with atau env, tidak pernah langsung di if:, dan tidak pernah di command line maupun di log.
  • Menjelaskan apa yang dilindungi masking, aturan pull request dari fork, dan permission GITHUB_TOKEN, serta di mana batas masing-masing.
Lihat cara kerjanya

Ikuti diagramnya langkah demi langkah

Tekan Putar, atau melangkah dengan panah. Setiap langkah juga ditulis di bawah diagram.

Ke mana secret pergi, dan ke mana tidak

Secret dienkripsi di mesin Anda, disimpan di settings repository, hanya diberikan ke job yang menyebutnya, disamarkan di log run, ditulis ke server sebagai file yang hanya bisa dibaca pemiliknya, dan tidak diberikan ke pull request dari fork.

Langkah 1 dari 6. Anda menyimpan nilai di settings repository. deploy.yml menyebut apa yang dibutuhkan setiap job. Job berjalan di runner GitHub, menulis log run, dan menjangkau server lewat SSH.

Tombol: ← → langkah · Home / End · K putar atau jeda

  1. Anda menyimpan nilai di settings repository. deploy.yml menyebut apa yang dibutuhkan setiap job. Job berjalan di runner GitHub, menulis log run, dan menjangkau server lewat SSH.

  2. gh secret set mengenkripsi nilai di mesin Anda sebelum dikirim. GitHub menyimpannya di Settings, Secrets and variables, Actions, dan API-nya tidak pernah mengembalikan nilai itu.

    gh secret set VPS_SSH_KEY
  3. Job deploy menyebut secrets.VPS_SSH_KEY. GitHub mengevaluasi ekspresi itu dan memasukkan nilainya sebelum step berjalan. Secret yang tidak disebut job mana pun tidak pernah sampai ke runner.

    key: secrets.VPS_SSH_KEYTebak langkah berikutnya Sebuah step meng-echo baris yang berisi key itu. Apa yang tampil di log run? (Key-nya, apa adanya · *** di tempat key itu · Tidak ada, step-nya diblokir)
  4. Runner mengganti setiap secret yang diterimanya dengan *** sebelum baris itu masuk log. Variable tercetak apa adanya. Salinan yang sudah diubah bentuknya bisa lolos: redaksi tidak dijamin.

    Secret: ***
  5. Lewat SSH, script deploy menulis nilai yang sudah dirender ke .env, lalu chmod 600 membuat hanya pemilik file yang bisa membaca atau mengubahnya. Masking melindungi log; di server, permission yang melindungi.

    chmod 600 .envTebak langkah berikutnya Misalkan workflow ini juga berjalan saat pull_request, lalu sebuah fork membukanya. Apa isi secrets.VPS_SSH_KEY di run itu? (Key yang sama, disamarkan · Salinan read-only · Nilai kosong)
  6. Pada pull_request dari fork, GitHub tidak memberikan secret apa pun kecuali GITHUB_TOKEN, dan token itu read-only. Ekspresinya bernilai kosong, jadi kode yang belum direview tidak pernah memegang key.

    on: pull_request

Apa itu secrets dan variables di GitHub Actions?

Secret adalah nilai yang Anda simpan untuk workflow dan harus tetap rahasia: API key, SSH key, token. GitHub mengenkripsi secret dengan libsodium sealed box sebelum sampai ke GitHub, mendekripsinya untuk dimasukkan ke workflow run, dan workflow hanya bisa membaca secret yang disebutnya secara eksplisit. REST API untuk melihat daftar secret sebuah repository hanya mengembalikan nama dan waktu, tidak pernah nilainya.

Baca selengkapnya (1)

Configuration variable dipakai untuk sisanya: GitHub menyebut variable sebagai cara menyimpan dan memakai ulang konfigurasi yang tidak sensitif, dan mengingatkan bahwa nilainya tampil tanpa disamarkan di output build. Port, path deploy, atau saklar fitur cocok jadi variable. Keduanya dibaca lewat context: secrets.NAME untuk secret dan vars.NAME untuk variable, ditulis di dalam ekspresi sebagai ${{ secrets.NAME }} atau ${{ vars.NAME }}. Nama yang belum pernah diisi terbaca sebagai string kosong, untuk keduanya.

Bagaimana cara membuat secret atau variable?

Lewat browser: buka Settings repository, lalu Secrets and variables, lalu Actions. Tab Secrets punya tombol New repository secret, dan tab Variables punya New repository variable. Secret dan variable milik environment ada di Settings, Environments, di dalam masing-masing environment. Anda butuh akses write ke repository milik organization, atau menjadi collaborator di repository milik akun pribadi.

Baca selengkapnya (2)

Dari terminal, GitHub CLI melakukan hal yang sama. gh secret set NAME meminta nilai lewat prompt, --body mengambilnya dari argumen, dan redirect seperti gh secret set NAME < file membacanya dari standard input. CLI mengenkripsi nilainya secara lokal sebelum dikirim. Tambahkan --env NAME untuk environment atau --org NAME untuk organization, dan pakai gh variable set dengan flag yang sama untuk variable. gh secret list hanya menampilkan nama; gh variable list ikut menampilkan nilai.

Aturan nama sama untuk keduanya: hanya huruf, angka, dan underscore, tanpa spasi, tanpa awalan GITHUB_, dan tidak boleh diawali angka. Nama tidak membedakan huruf besar dan kecil saat dipanggil dan disimpan dalam huruf besar, jadi secrets.vps_ssh_key dan secrets.VPS_SSH_KEY adalah secret yang sama.

Bagaimana job sebaiknya membaca secret?

Berikan hanya ke step yang membutuhkannya, sebagai input action di bawah with: atau sebagai environment variable di bawah env:. GitHub mengevaluasi setiap ekspresi dan memasukkan hasilnya sebelum step berjalan, jadi secret yang ditulis langsung di script run: menjadi bagian dari teks script itu, dan GitHub mengingatkan bahwa secret sering berisi karakter khusus yang bisa mengganggu shell Anda. Taruh di env: dan beri tanda kutip pada variable shell-nya.

Baca selengkapnya (2)

Jauhkan secret dari command line. Saran GitHub adalah meneruskannya antar proses lewat environment variable, standard input, atau mekanisme lain yang didukung program itu, karena command line bisa terlihat oleh user lain atau tercatat di audit event. Karena itu docker login di sini menerima token lewat --password-stdin, bukan sebagai argumen.

Ada dua aturan yang sering mengejutkan. Context secrets tidak tersedia di kondisi if:, jadi salin secret ke env: di level job, lalu uji env.NAME di if: milik step. Dan secret tidak otomatis mengalir ke reusable workflow: pemanggilnya harus meneruskan secret lewat nama, atau dengan secrets: inherit di dalam organization atau enterprise yang sama.

Repository, environment, atau organization: nilai mana yang menang?

Secret dan variable sama-sama ada di tiga level. Milik organization dibagi ke banyak repository, dengan policy yang membatasi repository mana yang boleh memakai masing-masing. Milik repository melayani setiap workflow di repository itu. Milik environment terikat ke environment bernama, misalnya production, dan hanya job yang mereferensikan environment itu lewat environment: production yang bisa membacanya. Kalau satu nama ada di beberapa level, level paling rendah yang menang: environment mengalahkan repository, repository mengalahkan organization.

Baca selengkapnya (2)

Environment adalah tempat secret mendapat gerbang. Job yang mereferensikan sebuah environment harus lolos protection rule-nya sebelum berjalan atau membaca secret environment itu. Required reviewers berisi hingga enam orang atau tim, dan satu persetujuan sudah cukup untuk melanjutkan job. Wait timer menunda job, dan deployment branches and tags membatasi ref mana yang boleh deploy. Mereferensikan environment yang belum ada akan membuatnya, tanpa rule apa pun.

Di repository private, sebagian fitur environment bergantung pada paket GitHub Anda, jadi cek halaman environments sebelum merancang alur di atasnya. Ingat juga siapa yang tetap bisa menjangkau secret repository: panduan hardening GitHub menyatakan setiap user dengan akses write ke repository punya akses baca ke semua secret yang dikonfigurasi di sana.

Apa yang disembunyikan masking di log, dan apa yang terlewat?

Runner menyamarkan nilai setiap secret yang diterimanya di job saat itu, jadi baris yang seharusnya mencetak secret akan menampilkan *** sebagai gantinya. GitHub menegaskan ini tidak dijamin: secret bisa diubah bentuknya dengan banyak cara, dan runner hanya bisa menyamarkan secret yang dipakai di job saat itu. Variable tampil tanpa disamarkan.

Baca selengkapnya (1)

Tiga kebiasaan menjaga redaksi tetap bekerja. Simpan satu nilai per secret, jangan pernah gumpalan JSON, XML, atau YAML, karena data terstruktur bisa membuat redaksi gagal. Saat sebuah step menghasilkan nilai sensitif baru, misalnya token berumur pendek yang diambil memakai secret, daftarkan nilai itu dengan workflow command ::add-mask:: sebelum ada yang mencetaknya. Dan kalau sebuah nilai sempat masuk log tanpa disamarkan, hapus log itu dan rotasi secret-nya.

Apa yang didapat pull request dari fork dan GITHUB_TOKEN?

Saat pull request dari fork memicu workflow, GitHub tidak memberikan secret apa pun ke runner kecuali GITHUB_TOKEN, dan token itu read-only. Workflow yang dipicu Dependabot juga tidak mendapat secret. Di repository publik, run dari kontributor pertama kali secara default menunggu persetujuan maintainer. Jadi kode yang belum Anda review boleh menjalankan test, tetapi tidak pernah memegang key Anda.

Baca selengkapnya (2)

pull_request_target adalah pengecualiannya, sekaligus jebakannya. Event ini menjalankan workflow dari default branch repository dasar dengan token dan secret repository dasar, yang hanya aman selama kode tepercaya itu yang dijalankan. Kalau Anda checkout head pull request lalu menjalankan build-nya di sana, kode dari fork berjalan dengan secret Anda; GitHub menyebutnya pwn request. Pakai hanya kalau memang perlu, dan jangan pernah untuk mem-build kode fork.

GITHUB_TOKEN adalah satu-satunya secret yang tidak pernah Anda buat. GitHub membuat token baru di awal setiap job, terbatas pada repository itu, dan token kedaluwarsa saat job selesai, atau paling lama enam jam di runner yang di-host GitHub. Blok permissions:, di level workflow atau per job, menentukan apa yang boleh dilakukannya, dan setiap permission yang tidak Anda tulis diset ke none. Repository baru di akun pribadi defaultnya hanya punya akses baca untuk contents dan packages.

Cara situs ini melakukannya

Konfigurasi nyata di balik pelajaran ini

Kutipan dari repositori yang membangun dan men-deploy situs ini, apa adanya.

.github/workflows/deploy.yml:4-6
# All app configuration is read from GitHub at run time:
#   Secrets  → GROQ_API_KEY, RESEND_API_KEY, VPS_SSH_HOST, VPS_SSH_USER, VPS_SSH_KEY
#   Variables→ CONTACT_FROM_EMAIL, CONTACT_TO_EMAIL, APP_PORT, DEPLOY_PATH, VPS_SSH_PORT

Sepuluh nama, tanpa nilai: nilainya hanya ada di GitHub dan di server. Baca pembagiannya sebagai satu aturan: apa pun yang memberi akses, atau yang sebaiknya tidak muncul di log run, menjadi secret. Alamat dan user server memang bukan password, tetapi sebagai secret keduanya tampil sebagai *** di log, bukan tercetak apa adanya. Repository ini milik akun pribadi dan tidak ada job yang menyebut environment, jadi kesepuluhnya berada di level repository.

.github/workflows/deploy.yml:203-211
      - name: Copy compose file to VPS
        uses: appleboy/scp-action@v0.1.7
        with:
          host: ${{ secrets.VPS_SSH_HOST }}
          username: ${{ secrets.VPS_SSH_USER }}
          key: ${{ secrets.VPS_SSH_KEY }}
          port: ${{ vars.VPS_SSH_PORT || 22 }}
          source: docker-compose.yml
          target: ${{ vars.DEPLOY_PATH }}

Secret dikirim sebagai input action di bawah with:, salah satu dari dua cara yang ditunjukkan dokumentasi GitHub, jadi tidak pernah berada di perintah shell. Port dan path tujuan adalah variable. Variable yang belum diisi terbaca sebagai string kosong, dan vars.VPS_SSH_PORT || 22 ditulis agar memakai port 22 dalam keadaan itu.

.github/workflows/deploy.yml:226-234
            cat > .env <<'ENVEOF'
            GROQ_API_KEY=${{ secrets.GROQ_API_KEY }}
            RESEND_API_KEY=${{ secrets.RESEND_API_KEY }}
            CONTACT_FROM_EMAIL=${{ vars.CONTACT_FROM_EMAIL }}
            CONTACT_TO_EMAIL=${{ vars.CONTACT_TO_EMAIL }}
            IMAGE=${{ needs.build-and-push.outputs.image }}
            APP_PORT=${{ vars.APP_PORT || 3002 }}
            ENVEOF
            chmod 600 .env

GitHub merender setiap ekspresi sebelum script ini meninggalkan runner, jadi server menerima nilai aslinya. Masking hanya mengubah log GitHub; ia tidak berpengaruh apa pun pada file di mesin lain. Karena itu baris terakhir penting: chmod 600 membuat .env hanya bisa dibaca dan diubah oleh pemiliknya. Pelajaran 10 membahas cara Compose membaca file ini.

.github/workflows/deploy.yml:236
            echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin

GITHUB_TOKEN milik job deploy itu sendiri, yang dibatasi blok permissions:-nya ke contents: read dan packages: read, cukup untuk menarik image dan tidak lebih. Token dikirim ke docker login lewat standard input, sesuai saran GitHub, bukan sebagai argumen command line. Token kedaluwarsa saat job selesai, dan script menjalankan docker logout setelah container berjalan.

Yang sering rusak

Kesalahan yang benar-benar membuang waktu

  • Secret kosong di dalam job

    Ekspresi untuk secret yang tidak dimiliki run akan mengembalikan string kosong, jadi step tetap berjalan dengan nilai kosong. Cek berurutan: job berjalan dari pull request fork atau dari Dependabot, yang tidak mendapat secret; secret-nya milik environment tetapi job tidak punya key environment:; reusable workflow dipanggil tanpa meneruskan secret; atau namanya ditulis berbeda.

  • Kondisi yang membaca secret

    if: secrets.DEPLOY_KEY != '' terlihat masuk akal, tetapi context secrets tidak tersedia di key if:. Jadikan secret itu entri env: di level job, lalu tulis kondisi step sebagai if: env.DEPLOY_KEY != '', pola yang didokumentasikan GitHub.

  • Secret tetap muncul di log

    Redaksi hanya mencocokkan apa yang diketahui runner, jadi salinan yang diubah bentuknya, nilai di dalam gumpalan JSON, atau token turunan dari secret bisa tercetak apa adanya. Hapus log-nya dan rotasi secret-nya lebih dulu. Lalu simpan satu nilai per secret, daftarkan nilai turunan dengan ::add-mask::, dan berhenti mencetak nilainya sama sekali.

  • pull_request_target mem-build kode fork dengan secret Anda

    Sebuah workflow diganti ke pull_request_target agar pull request dari fork bisa menjangkau secret, lalu checkout head pull request dan menjalankan npm install. Kode fork kini berjalan dengan token dan secret Anda. Tetap jalankan build kode fork di pull_request, dan pakai pull_request_target hanya untuk pekerjaan tepercaya seperti memberi label.

Secret dan configuration variable berdampingan, termasuk scope-nya, sesuai dokumentasi GitHub pada 2026-10-18.
Dipakai untukAPI key, SSH key, token: apa pun yang tidak akan Anda unggah ke publikKonfigurasi tidak sensitif: port, path, saklar
Dibaca lewatsecrets.NAME, di with: atau env:, tidak di if:vars.NAME, juga di if: dan environment:
Di log runTampil sebagai ***, tidak dijamin setelah diubah bentuknyaTercetak apa adanya
Belum pernah diisiString kosongString kosong
LevelOrganization, repository, environmentOrganization, repository, environment
Nama sama di dua levelLevel terendah menang: environment, lalu repository, lalu organizationAturan yang sama
Level environmentHanya job yang menyebut environment itu, setelah lolos protection ruleHanya job yang menyebut environment itu
Batas100 per repository, 100 per environment, 48 KB per secret500 per repository, 100 per environment, 48 KB per variable
Coba sendiri

Lakukan di komputer Anda sendiri

Pakai repository uji publik baru milik Anda sendiri dan nilai sekali pakai, jangan pernah key asli. Anda butuh GitHub CLI yang sudah login lewat gh auth login. Akun GitHub kedua, atau milik teman, hanya dibutuhkan untuk langkah fork.

  1. Buat repository uji dengan README, supaya sudah punya branch main, lalu clone.

    gh repo create secrets-lab --public --clone --add-readme
    cd secrets-lab
  2. Isi satu secret dan satu variable. gh secret list menampilkan nama dan kapan diubah, tidak pernah nilainya; gh variable list ikut menampilkan nilainya.

    gh secret set DEMO_SECRET --body "practice-only"
    gh variable set DEMO_GREETING --body "hello from a variable"
    gh secret list
    gh variable list
  3. Tambahkan workflow yang membaca keduanya lewat env: di level job, menjalankan satu step hanya jika secret-nya sampai, dan menyamarkan nilai yang dibuatnya sendiri. Commit lalu push.

    mkdir -p .github/workflows
    cat > .github/workflows/lab.yml <<'EOF'
    name: Secrets lab
    on: [push, pull_request]
    permissions:
      contents: read
    jobs:
      show:
        runs-on: ubuntu-latest
        env:
          DEMO_SECRET: ${{ secrets.DEMO_SECRET }}
          DEMO_GREETING: ${{ vars.DEMO_GREETING }}
        steps:
          - name: Print a variable and a secret
            run: |
              echo "Variable: $DEMO_GREETING"
              echo "Secret: $DEMO_SECRET"
          - name: Run only when the secret arrived
            if: env.DEMO_SECRET != ''
            run: echo "The secret reached this run"
          - name: Mask a value you made yourself
            run: |
              derived="lab-$RANDOM"
              echo "::add-mask::$derived"
              echo "Derived: $derived"
    EOF
    git add .github
    git commit -m "Add a secrets lab workflow"
    git push
  4. Tunggu beberapa detik sampai run dimulai, lalu pantau dan baca log-nya. Variable tercetak sesuai ketikan, sedangkan secret dan nilai turunan tampil sebagai ***.

    run_id=$(gh run list --limit 1 --json databaseId --jq '.[0].databaseId')
    gh run watch "$run_id"
    gh run view "$run_id" --log | grep -E "Variable:|Secret:|Derived:|reached"
  5. Di browser, buka Settings, Environments, New environment. Beri nama lab, centang Required reviewers, tambahkan diri Anda, lalu simpan. Beri lab DEMO_SECRET miliknya sendiri dengan nilai yang lebih panjang, lalu tambahkan job yang menyebut environment itu. Run menunggu persetujuan Anda; setujui di halaman run, dan log mencetak Length 20, nilai milik environment, bukan 13.

    gh secret set DEMO_SECRET --env lab --body "lab-environment-only"
    cat >> .github/workflows/lab.yml <<'EOF'
      gated:
        if: github.event_name == 'push'
        runs-on: ubuntu-latest
        environment: lab
        steps:
          - name: Which DEMO_SECRET won?
            env:
              DEMO_SECRET: ${{ secrets.DEMO_SECRET }}
            run: echo "Length ${#DEMO_SECRET}"
    EOF
    git commit -am "Add a gated job"
    git push
  6. Dari akun kedua, fork secrets-lab, ubah README.md di fork itu, lalu buka pull request. Kalau GitHub meminta, setujui run-nya: kontributor pertama kali secara default butuh persetujuan. Di run itu, Secret: tidak mencetak apa pun setelah titik dua, dan step yang membutuhkan secret dilewati.

  7. Hapus repository uji. Penghapusan butuh scope delete_repo, dan --yes hanya melewati konfirmasi jika Anda menyebut repository-nya sebagai owner/name.

    cd ..
    gh auth refresh -s delete_repo
    gh repo delete YOUR-USER/secrets-lab --yes
Kuis singkat

Tiga pertanyaan sebelum lanjut

Pilih jawaban untuk melihat kenapa benar atau salah. Tidak ada nilai, jadi tebak saja dengan bebas; jawaban yang salah disimpan di browser ini agar kursus bisa menanyakannya lagi nanti.

  1. 1.DEPLOY_KEY ada sebagai secret repository dan sebagai secret di environment production. Sebuah job dengan environment: production membaca secrets.DEPLOY_KEY. Nilai mana yang didapatnya?
  2. 2.Repository publik Anda menjalankan test saat pull_request. Seseorang membuka pull request dari fork-nya. Apa yang didapat run itu?
  3. 3.Sebuah step memakai secret untuk mengambil token berumur pendek, dan step berikutnya mencetak perintah yang berisi token itu. Bagaimana menjauhkannya dari log?
Dari pelajaran sebelumnya

Dua pertanyaan dari pelajaran sebelumnya

Mengingat kembali pelajaran lama setelah jeda membuatnya lebih melekat. Jawab dari ingatan sebelum mencari apa pun.

  1. 1.Sebuah image berbasis Alpine mengecek wget --spider http://localhost:3000/, dan aplikasinya listen di 0.0.0.0. Log health berisi Connection refused, padahal aplikasinya menjawab. Apa yang salah?
  2. 2.File compose situs ini berisi - "127.0.0.1:${APP_PORT:-3002}:3000". Apa yang mengubah ${APP_PORT:-3002} menjadi 3002?

Dari: 23. Cara Membuat Healthcheck Docker dan Memilih Restart Policy · 21. Belajar YAML untuk Pemula: Map, List, Quote, dan Jebakannya

Pikirkan dulu

Jawab dalam hati, lalu cek

  1. 1.VPS_SSH_HOST di situs ini bukan password. Kenapa dijadikan secret, bukan variable?Lihat jawabannya

    Jawaban:Karena variable tercetak tanpa disamarkan, sedangkan secret disamarkan. Sebagai secret, alamat server tampil sebagai *** di setiap log run, bukan tertulis lengkap.

  2. 2.Bagaimana menjalankan sebuah step hanya jika secret-nya terisi?Lihat jawabannya

    Jawaban:Context secrets tidak tersedia di if:. Salin secret ke entri env: di level job, lalu uji di kondisi step: if: env.NAME != ''.

  3. 3.Job deploy menulis contents: read dan packages: read. Apa yang boleh dilakukan GITHUB_TOKEN-nya terhadap issues?Lihat jawabannya

    Jawaban:Tidak ada. Begitu blok permissions: menulis satu permission saja, setiap permission yang tidak ditulis diset ke none, jadi token itu hanya bisa membaca repository dan menarik package.

Pelajari lebih dalam

Bahasan mendalam terkait di situs ini