[{"data":1,"prerenderedAt":1612},["ShallowReactive",2],{"page-tr-\u002Fblog\u002Fgithub-actions-ci-cd":3,"related-tr-\u002Fblog\u002Fgithub-actions-ci-cd":1589},{"id":4,"title":5,"body":6,"date":1575,"description":1576,"extension":1577,"meta":1578,"navigation":193,"path":1579,"seo":1580,"stem":1581,"tags":1582,"translation":1587,"__hash__":1588},"pages_tr\u002Ftr\u002Fblog\u002Fgithub-actions-ci-cd.md","GitHub Actions ile CI\u002FCD — Build, Test ve Sunucuya Deploy",{"type":7,"value":8,"toc":1562},"minimark",[9,14,23,26,39,42,46,151,155,392,395,461,467,471,605,608,612,624,703,714,721,774,778,1118,1121,1154,1158,1167,1207,1210,1214,1217,1388,1392,1471,1475,1544,1548,1558],[10,11,13],"h2",{"id":12},"giriş","Giriş",[15,16,17,18,22],"p",{},"GitHub Actions, repo içindeki ",[19,20,21],"code",{},".github\u002Fworkflows\u002F*.yml"," dosyalarıyla tanımlanan, GitHub'ın kendi altyapısında çalışan bir CI\u002FCD sistemidir. Public repolar için ücretsizdir, private repolarda da aylık ücretsiz dakika kotası vardır.",[15,24,25],{},"Bu yazıda şu akışı kuracağız:",[27,28,29,33],"ol",{},[30,31,32],"li",{},"Her push ve pull request'te: bağımlılıkları kur, build et, testleri çalıştır.",[30,34,35,38],{},[19,36,37],{},"main"," branch'ine merge edilince: çıktıyı SSH ile kendi sunucumuza kopyala ve uygulamayı yeniden başlat.",[15,40,41],{},"Bu blog da tam olarak bu yöntemle deploy ediliyor.",[10,43,45],{"id":44},"_1-temel-kavramlar","1. Temel kavramlar",[47,48,49,62],"table",{},[50,51,52],"thead",{},[53,54,55,59],"tr",{},[56,57,58],"th",{},"Kavram",[56,60,61],{},"Anlamı",[63,64,65,77,102,110,125,143],"tbody",{},[53,66,67,71],{},[68,69,70],"td",{},"Workflow",[68,72,73,76],{},[19,74,75],{},".github\u002Fworkflows\u002F"," altındaki bir YAML dosyası",[53,78,79,86],{},[68,80,81,82,85],{},"Event (",[19,83,84],{},"on",")",[68,87,88,89,92,93,92,96,92,99],{},"Workflow'u tetikleyen olay: ",[19,90,91],{},"push",", ",[19,94,95],{},"pull_request",[19,97,98],{},"schedule",[19,100,101],{},"workflow_dispatch",[53,103,104,107],{},[68,105,106],{},"Job",[68,108,109],{},"Aynı runner üzerinde sırayla çalışan adımlar grubu; job'lar varsayılan olarak paralel çalışır",[53,111,112,115],{},[68,113,114],{},"Step",[68,116,117,118,121,122,85],{},"Tek bir komut (",[19,119,120],{},"run",") ya da hazır bir action (",[19,123,124],{},"uses",[53,126,127,130],{},[68,128,129],{},"Runner",[68,131,132,133,92,136,92,139,142],{},"Job'u çalıştıran makine: ",[19,134,135],{},"ubuntu-latest",[19,137,138],{},"windows-latest",[19,140,141],{},"macos-latest"," ya da self-hosted",[53,144,145,148],{},[68,146,147],{},"Secret",[68,149,150],{},"Repo ayarlarında saklanan, loglarda maskelenen gizli değer",[10,152,154],{"id":153},"_2-ci-build-ve-test","2. CI: build ve test",[156,157,162],"pre",{"className":158,"code":159,"language":160,"meta":161,"style":161},"language-yaml shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","# .github\u002Fworkflows\u002Fci.yml\nname: CI\n\non:\n  push:\n    branches: [main]\n  pull_request:\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\u002Fcheckout@v4\n\n      - uses: actions\u002Fsetup-node@v4\n        with:\n          node-version: 22\n          cache: npm\n\n      - run: npm ci\n      - run: npm run lint\n      - run: npm test\n      - run: npm run build\n","yaml","",[19,163,164,173,188,195,204,212,228,236,241,249,257,268,276,290,295,307,315,327,338,343,356,368,380],{"__ignoreMap":161},[165,166,169],"span",{"class":167,"line":168},"line",1,[165,170,172],{"class":171},"sHwdD","# .github\u002Fworkflows\u002Fci.yml\n",[165,174,176,180,184],{"class":167,"line":175},2,[165,177,179],{"class":178},"swJcz","name",[165,181,183],{"class":182},"sMK4o",":",[165,185,187],{"class":186},"sfazB"," CI\n",[165,189,191],{"class":167,"line":190},3,[165,192,194],{"emptyLinePlaceholder":193},true,"\n",[165,196,198,201],{"class":167,"line":197},4,[165,199,84],{"class":200},"sfNiH",[165,202,203],{"class":182},":\n",[165,205,207,210],{"class":167,"line":206},5,[165,208,209],{"class":178},"  push",[165,211,203],{"class":182},[165,213,215,218,220,223,225],{"class":167,"line":214},6,[165,216,217],{"class":178},"    branches",[165,219,183],{"class":182},[165,221,222],{"class":182}," [",[165,224,37],{"class":186},[165,226,227],{"class":182},"]\n",[165,229,231,234],{"class":167,"line":230},7,[165,232,233],{"class":178},"  pull_request",[165,235,203],{"class":182},[165,237,239],{"class":167,"line":238},8,[165,240,194],{"emptyLinePlaceholder":193},[165,242,244,247],{"class":167,"line":243},9,[165,245,246],{"class":178},"jobs",[165,248,203],{"class":182},[165,250,252,255],{"class":167,"line":251},10,[165,253,254],{"class":178},"  build",[165,256,203],{"class":182},[165,258,260,263,265],{"class":167,"line":259},11,[165,261,262],{"class":178},"    runs-on",[165,264,183],{"class":182},[165,266,267],{"class":186}," ubuntu-latest\n",[165,269,271,274],{"class":167,"line":270},12,[165,272,273],{"class":178},"    steps",[165,275,203],{"class":182},[165,277,279,282,285,287],{"class":167,"line":278},13,[165,280,281],{"class":182},"      -",[165,283,284],{"class":178}," uses",[165,286,183],{"class":182},[165,288,289],{"class":186}," actions\u002Fcheckout@v4\n",[165,291,293],{"class":167,"line":292},14,[165,294,194],{"emptyLinePlaceholder":193},[165,296,298,300,302,304],{"class":167,"line":297},15,[165,299,281],{"class":182},[165,301,284],{"class":178},[165,303,183],{"class":182},[165,305,306],{"class":186}," actions\u002Fsetup-node@v4\n",[165,308,310,313],{"class":167,"line":309},16,[165,311,312],{"class":178},"        with",[165,314,203],{"class":182},[165,316,318,321,323],{"class":167,"line":317},17,[165,319,320],{"class":178},"          node-version",[165,322,183],{"class":182},[165,324,326],{"class":325},"sbssI"," 22\n",[165,328,330,333,335],{"class":167,"line":329},18,[165,331,332],{"class":178},"          cache",[165,334,183],{"class":182},[165,336,337],{"class":186}," npm\n",[165,339,341],{"class":167,"line":340},19,[165,342,194],{"emptyLinePlaceholder":193},[165,344,346,348,351,353],{"class":167,"line":345},20,[165,347,281],{"class":182},[165,349,350],{"class":178}," run",[165,352,183],{"class":182},[165,354,355],{"class":186}," npm ci\n",[165,357,359,361,363,365],{"class":167,"line":358},21,[165,360,281],{"class":182},[165,362,350],{"class":178},[165,364,183],{"class":182},[165,366,367],{"class":186}," npm run lint\n",[165,369,371,373,375,377],{"class":167,"line":370},22,[165,372,281],{"class":182},[165,374,350],{"class":178},[165,376,183],{"class":182},[165,378,379],{"class":186}," npm test\n",[165,381,383,385,387,389],{"class":167,"line":382},23,[165,384,281],{"class":182},[165,386,350],{"class":178},[165,388,183],{"class":182},[165,390,391],{"class":186}," npm run build\n",[15,393,394],{},"Java\u002FMaven projesi için aynı iskelet:",[156,396,398],{"className":158,"code":397,"language":160,"meta":161,"style":161},"      - uses: actions\u002Fsetup-java@v4\n        with:\n          distribution: temurin\n          java-version: 21\n          cache: maven\n\n      - run: .\u002Fmvnw -B verify\n",[19,399,400,411,417,427,437,446,450],{"__ignoreMap":161},[165,401,402,404,406,408],{"class":167,"line":168},[165,403,281],{"class":182},[165,405,284],{"class":178},[165,407,183],{"class":182},[165,409,410],{"class":186}," actions\u002Fsetup-java@v4\n",[165,412,413,415],{"class":167,"line":175},[165,414,312],{"class":178},[165,416,203],{"class":182},[165,418,419,422,424],{"class":167,"line":190},[165,420,421],{"class":178},"          distribution",[165,423,183],{"class":182},[165,425,426],{"class":186}," temurin\n",[165,428,429,432,434],{"class":167,"line":197},[165,430,431],{"class":178},"          java-version",[165,433,183],{"class":182},[165,435,436],{"class":325}," 21\n",[165,438,439,441,443],{"class":167,"line":206},[165,440,332],{"class":178},[165,442,183],{"class":182},[165,444,445],{"class":186}," maven\n",[165,447,448],{"class":167,"line":214},[165,449,194],{"emptyLinePlaceholder":193},[165,451,452,454,456,458],{"class":167,"line":230},[165,453,281],{"class":182},[165,455,350],{"class":178},[165,457,183],{"class":182},[165,459,460],{"class":186}," .\u002Fmvnw -B verify\n",[15,462,463,466],{},[19,464,465],{},"cache"," parametresi bağımlılıkları (npm, Maven, Gradle) çalıştırmalar arasında saklar; build süresini ciddi ölçüde kısaltır.",[10,468,470],{"id":469},"_3-matrix-ile-birden-fazla-sürümde-test","3. Matrix ile birden fazla sürümde test",[156,472,474],{"className":158,"code":473,"language":160,"meta":161,"style":161},"jobs:\n  test:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        node: [20, 22, 24]\n    steps:\n      - uses: actions\u002Fcheckout@v4\n      - uses: actions\u002Fsetup-node@v4\n        with:\n          node-version: ${{ matrix.node }}\n          cache: npm\n      - run: npm ci\n      - run: npm test\n",[19,475,476,482,489,497,504,511,536,542,552,562,568,577,585,595],{"__ignoreMap":161},[165,477,478,480],{"class":167,"line":168},[165,479,246],{"class":178},[165,481,203],{"class":182},[165,483,484,487],{"class":167,"line":175},[165,485,486],{"class":178},"  test",[165,488,203],{"class":182},[165,490,491,493,495],{"class":167,"line":190},[165,492,262],{"class":178},[165,494,183],{"class":182},[165,496,267],{"class":186},[165,498,499,502],{"class":167,"line":197},[165,500,501],{"class":178},"    strategy",[165,503,203],{"class":182},[165,505,506,509],{"class":167,"line":206},[165,507,508],{"class":178},"      matrix",[165,510,203],{"class":182},[165,512,513,516,518,520,523,526,529,531,534],{"class":167,"line":214},[165,514,515],{"class":178},"        node",[165,517,183],{"class":182},[165,519,222],{"class":182},[165,521,522],{"class":325},"20",[165,524,525],{"class":182},",",[165,527,528],{"class":325}," 22",[165,530,525],{"class":182},[165,532,533],{"class":325}," 24",[165,535,227],{"class":182},[165,537,538,540],{"class":167,"line":230},[165,539,273],{"class":178},[165,541,203],{"class":182},[165,543,544,546,548,550],{"class":167,"line":238},[165,545,281],{"class":182},[165,547,284],{"class":178},[165,549,183],{"class":182},[165,551,289],{"class":186},[165,553,554,556,558,560],{"class":167,"line":243},[165,555,281],{"class":182},[165,557,284],{"class":178},[165,559,183],{"class":182},[165,561,306],{"class":186},[165,563,564,566],{"class":167,"line":251},[165,565,312],{"class":178},[165,567,203],{"class":182},[165,569,570,572,574],{"class":167,"line":259},[165,571,320],{"class":178},[165,573,183],{"class":182},[165,575,576],{"class":186}," ${{ matrix.node }}\n",[165,578,579,581,583],{"class":167,"line":270},[165,580,332],{"class":178},[165,582,183],{"class":182},[165,584,337],{"class":186},[165,586,587,589,591,593],{"class":167,"line":278},[165,588,281],{"class":182},[165,590,350],{"class":178},[165,592,183],{"class":182},[165,594,355],{"class":186},[165,596,597,599,601,603],{"class":167,"line":292},[165,598,281],{"class":182},[165,600,350],{"class":178},[165,602,183],{"class":182},[165,604,379],{"class":186},[15,606,607],{},"Bu tanım aynı job'u üç Node sürümüyle paralel olarak çalıştırır.",[10,609,611],{"id":610},"_4-secretlar","4. Secret'lar",[15,613,614,615,619,620,623],{},"Sunucu adresi, SSH anahtarı gibi değerler repoya yazılmaz; ",[616,617,618],"strong",{},"Settings → Secrets and variables → Actions"," altına eklenir ya da ",[19,621,622],{},"gh"," CLI ile tanımlanır:",[156,625,629],{"className":626,"code":627,"language":628,"meta":161,"style":161},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","gh secret set DEPLOY_HOST --body \"203.0.113.10\"\ngh secret set DEPLOY_USER --body \"deploy\"\ngh secret set DEPLOY_SSH_KEY \u003C ~\u002F.ssh\u002Fdeploy_key\ngh secret list\n","bash",[19,630,631,657,677,694],{"__ignoreMap":161},[165,632,633,636,639,642,645,648,651,654],{"class":167,"line":168},[165,634,622],{"class":635},"sBMFI",[165,637,638],{"class":186}," secret",[165,640,641],{"class":186}," set",[165,643,644],{"class":186}," DEPLOY_HOST",[165,646,647],{"class":186}," --body",[165,649,650],{"class":182}," \"",[165,652,653],{"class":186},"203.0.113.10",[165,655,656],{"class":182},"\"\n",[165,658,659,661,663,665,668,670,672,675],{"class":167,"line":175},[165,660,622],{"class":635},[165,662,638],{"class":186},[165,664,641],{"class":186},[165,666,667],{"class":186}," DEPLOY_USER",[165,669,647],{"class":186},[165,671,650],{"class":182},[165,673,674],{"class":186},"deploy",[165,676,656],{"class":182},[165,678,679,681,683,685,688,691],{"class":167,"line":190},[165,680,622],{"class":635},[165,682,638],{"class":186},[165,684,641],{"class":186},[165,686,687],{"class":186}," DEPLOY_SSH_KEY",[165,689,690],{"class":182}," \u003C",[165,692,693],{"class":186}," ~\u002F.ssh\u002Fdeploy_key\n",[165,695,696,698,700],{"class":167,"line":197},[165,697,622],{"class":635},[165,699,638],{"class":186},[165,701,702],{"class":186}," list\n",[15,704,705,706,709,710,713],{},"Workflow içinde ",[19,707,708],{},"${{ secrets.DEPLOY_HOST }}"," şeklinde kullanılır. Secret'lar loglarda ",[19,711,712],{},"***"," olarak maskelenir ve fork'lardan açılan PR'lara verilmez.",[15,715,716,717,720],{},"Deploy için ayrı bir anahtar çifti üretip public anahtarı sunucudaki deploy kullanıcısının ",[19,718,719],{},"~\u002F.ssh\u002Fauthorized_keys"," dosyasına ekleyin:",[156,722,724],{"className":626,"code":723,"language":628,"meta":161,"style":161},"ssh-keygen -t ed25519 -f ~\u002F.ssh\u002Fdeploy_key -C \"github-actions\" -N \"\"\nssh-copy-id -i ~\u002F.ssh\u002Fdeploy_key.pub deploy@203.0.113.10\n",[19,725,726,760],{"__ignoreMap":161},[165,727,728,731,734,737,740,743,746,748,751,754,757],{"class":167,"line":168},[165,729,730],{"class":635},"ssh-keygen",[165,732,733],{"class":186}," -t",[165,735,736],{"class":186}," ed25519",[165,738,739],{"class":186}," -f",[165,741,742],{"class":186}," ~\u002F.ssh\u002Fdeploy_key",[165,744,745],{"class":186}," -C",[165,747,650],{"class":182},[165,749,750],{"class":186},"github-actions",[165,752,753],{"class":182},"\"",[165,755,756],{"class":186}," -N",[165,758,759],{"class":182}," \"\"\n",[165,761,762,765,768,771],{"class":167,"line":175},[165,763,764],{"class":635},"ssh-copy-id",[165,766,767],{"class":186}," -i",[165,769,770],{"class":186}," ~\u002F.ssh\u002Fdeploy_key.pub",[165,772,773],{"class":186}," deploy@203.0.113.10\n",[10,775,777],{"id":776},"_5-cd-ssh-ile-sunucuya-deploy","5. CD: SSH ile sunucuya deploy",[156,779,781],{"className":158,"code":780,"language":160,"meta":161,"style":161},"# .github\u002Fworkflows\u002Fdeploy.yml\nname: Deploy\n\non:\n  push:\n    branches: [main]\n  workflow_dispatch:\n\nconcurrency:\n  group: deploy-production\n  cancel-in-progress: false\n\njobs:\n  deploy:\n    runs-on: ubuntu-latest\n    environment: production\n    steps:\n      - uses: actions\u002Fcheckout@v4\n\n      - uses: actions\u002Fsetup-node@v4\n        with:\n          node-version: 22\n          cache: npm\n\n      - run: npm ci\n      - run: npm test\n      - run: npm run build\n\n      - name: Configure SSH\n        run: |\n          mkdir -p ~\u002F.ssh\n          echo \"${{ secrets.DEPLOY_SSH_KEY }}\" > ~\u002F.ssh\u002Fid_ed25519\n          chmod 600 ~\u002F.ssh\u002Fid_ed25519\n          ssh-keyscan -H \"${{ secrets.DEPLOY_HOST }}\" >> ~\u002F.ssh\u002Fknown_hosts\n\n      - name: Upload\n        run: |\n          rsync -az --delete dist\u002F \\\n            \"${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}:\u002Fvar\u002Fwww\u002Fapp\u002F\"\n\n      - name: Restart\n        run: |\n          ssh \"${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}\" \\\n            \"cd \u002Fvar\u002Fwww\u002Fapp && npm ci --omit=dev && pm2 reload app\"\n",[19,782,783,788,797,801,807,813,825,832,836,843,853,863,867,873,880,888,898,904,914,918,928,934,942,950,955,966,977,988,993,1006,1018,1024,1030,1036,1042,1047,1059,1068,1074,1080,1085,1097,1106,1112],{"__ignoreMap":161},[165,784,785],{"class":167,"line":168},[165,786,787],{"class":171},"# .github\u002Fworkflows\u002Fdeploy.yml\n",[165,789,790,792,794],{"class":167,"line":175},[165,791,179],{"class":178},[165,793,183],{"class":182},[165,795,796],{"class":186}," Deploy\n",[165,798,799],{"class":167,"line":190},[165,800,194],{"emptyLinePlaceholder":193},[165,802,803,805],{"class":167,"line":197},[165,804,84],{"class":200},[165,806,203],{"class":182},[165,808,809,811],{"class":167,"line":206},[165,810,209],{"class":178},[165,812,203],{"class":182},[165,814,815,817,819,821,823],{"class":167,"line":214},[165,816,217],{"class":178},[165,818,183],{"class":182},[165,820,222],{"class":182},[165,822,37],{"class":186},[165,824,227],{"class":182},[165,826,827,830],{"class":167,"line":230},[165,828,829],{"class":178},"  workflow_dispatch",[165,831,203],{"class":182},[165,833,834],{"class":167,"line":238},[165,835,194],{"emptyLinePlaceholder":193},[165,837,838,841],{"class":167,"line":243},[165,839,840],{"class":178},"concurrency",[165,842,203],{"class":182},[165,844,845,848,850],{"class":167,"line":251},[165,846,847],{"class":178},"  group",[165,849,183],{"class":182},[165,851,852],{"class":186}," deploy-production\n",[165,854,855,858,860],{"class":167,"line":259},[165,856,857],{"class":178},"  cancel-in-progress",[165,859,183],{"class":182},[165,861,862],{"class":200}," false\n",[165,864,865],{"class":167,"line":270},[165,866,194],{"emptyLinePlaceholder":193},[165,868,869,871],{"class":167,"line":278},[165,870,246],{"class":178},[165,872,203],{"class":182},[165,874,875,878],{"class":167,"line":292},[165,876,877],{"class":178},"  deploy",[165,879,203],{"class":182},[165,881,882,884,886],{"class":167,"line":297},[165,883,262],{"class":178},[165,885,183],{"class":182},[165,887,267],{"class":186},[165,889,890,893,895],{"class":167,"line":309},[165,891,892],{"class":178},"    environment",[165,894,183],{"class":182},[165,896,897],{"class":186}," production\n",[165,899,900,902],{"class":167,"line":317},[165,901,273],{"class":178},[165,903,203],{"class":182},[165,905,906,908,910,912],{"class":167,"line":329},[165,907,281],{"class":182},[165,909,284],{"class":178},[165,911,183],{"class":182},[165,913,289],{"class":186},[165,915,916],{"class":167,"line":340},[165,917,194],{"emptyLinePlaceholder":193},[165,919,920,922,924,926],{"class":167,"line":345},[165,921,281],{"class":182},[165,923,284],{"class":178},[165,925,183],{"class":182},[165,927,306],{"class":186},[165,929,930,932],{"class":167,"line":358},[165,931,312],{"class":178},[165,933,203],{"class":182},[165,935,936,938,940],{"class":167,"line":370},[165,937,320],{"class":178},[165,939,183],{"class":182},[165,941,326],{"class":325},[165,943,944,946,948],{"class":167,"line":382},[165,945,332],{"class":178},[165,947,183],{"class":182},[165,949,337],{"class":186},[165,951,953],{"class":167,"line":952},24,[165,954,194],{"emptyLinePlaceholder":193},[165,956,958,960,962,964],{"class":167,"line":957},25,[165,959,281],{"class":182},[165,961,350],{"class":178},[165,963,183],{"class":182},[165,965,355],{"class":186},[165,967,969,971,973,975],{"class":167,"line":968},26,[165,970,281],{"class":182},[165,972,350],{"class":178},[165,974,183],{"class":182},[165,976,379],{"class":186},[165,978,980,982,984,986],{"class":167,"line":979},27,[165,981,281],{"class":182},[165,983,350],{"class":178},[165,985,183],{"class":182},[165,987,391],{"class":186},[165,989,991],{"class":167,"line":990},28,[165,992,194],{"emptyLinePlaceholder":193},[165,994,996,998,1001,1003],{"class":167,"line":995},29,[165,997,281],{"class":182},[165,999,1000],{"class":178}," name",[165,1002,183],{"class":182},[165,1004,1005],{"class":186}," Configure SSH\n",[165,1007,1009,1012,1014],{"class":167,"line":1008},30,[165,1010,1011],{"class":178},"        run",[165,1013,183],{"class":182},[165,1015,1017],{"class":1016},"s7zQu"," |\n",[165,1019,1021],{"class":167,"line":1020},31,[165,1022,1023],{"class":186},"          mkdir -p ~\u002F.ssh\n",[165,1025,1027],{"class":167,"line":1026},32,[165,1028,1029],{"class":186},"          echo \"${{ secrets.DEPLOY_SSH_KEY }}\" > ~\u002F.ssh\u002Fid_ed25519\n",[165,1031,1033],{"class":167,"line":1032},33,[165,1034,1035],{"class":186},"          chmod 600 ~\u002F.ssh\u002Fid_ed25519\n",[165,1037,1039],{"class":167,"line":1038},34,[165,1040,1041],{"class":186},"          ssh-keyscan -H \"${{ secrets.DEPLOY_HOST }}\" >> ~\u002F.ssh\u002Fknown_hosts\n",[165,1043,1045],{"class":167,"line":1044},35,[165,1046,194],{"emptyLinePlaceholder":193},[165,1048,1050,1052,1054,1056],{"class":167,"line":1049},36,[165,1051,281],{"class":182},[165,1053,1000],{"class":178},[165,1055,183],{"class":182},[165,1057,1058],{"class":186}," Upload\n",[165,1060,1062,1064,1066],{"class":167,"line":1061},37,[165,1063,1011],{"class":178},[165,1065,183],{"class":182},[165,1067,1017],{"class":1016},[165,1069,1071],{"class":167,"line":1070},38,[165,1072,1073],{"class":186},"          rsync -az --delete dist\u002F \\\n",[165,1075,1077],{"class":167,"line":1076},39,[165,1078,1079],{"class":186},"            \"${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}:\u002Fvar\u002Fwww\u002Fapp\u002F\"\n",[165,1081,1083],{"class":167,"line":1082},40,[165,1084,194],{"emptyLinePlaceholder":193},[165,1086,1088,1090,1092,1094],{"class":167,"line":1087},41,[165,1089,281],{"class":182},[165,1091,1000],{"class":178},[165,1093,183],{"class":182},[165,1095,1096],{"class":186}," Restart\n",[165,1098,1100,1102,1104],{"class":167,"line":1099},42,[165,1101,1011],{"class":178},[165,1103,183],{"class":182},[165,1105,1017],{"class":1016},[165,1107,1109],{"class":167,"line":1108},43,[165,1110,1111],{"class":186},"          ssh \"${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}\" \\\n",[165,1113,1115],{"class":167,"line":1114},44,[165,1116,1117],{"class":186},"            \"cd \u002Fvar\u002Fwww\u002Fapp && npm ci --omit=dev && pm2 reload app\"\n",[15,1119,1120],{},"Önemli noktalar:",[1122,1123,1124,1131,1139,1147],"ul",{},[30,1125,1126,1130],{},[616,1127,1128],{},[19,1129,840],{},", art arda iki merge geldiğinde deploy'ların üst üste binmesini engeller.",[30,1132,1133,1138],{},[616,1134,1135],{},[19,1136,1137],{},"environment: production",", GitHub'da onay kuralı (required reviewers) ve environment'a özel secret tanımlamanıza izin verir.",[30,1140,1141,1146],{},[616,1142,1143],{},[19,1144,1145],{},"rsync --delete",", sunucuda artık build çıktısında olmayan dosyaları da siler.",[30,1148,1149,1153],{},[616,1150,1151],{},[19,1152,101],{},", workflow'u Actions sekmesinden elle çalıştırmak için bir buton ekler.",[10,1155,1157],{"id":1156},"_6-zamanlanmış-çalıştırma","6. Zamanlanmış çalıştırma",[15,1159,1160,1162,1163,1166],{},[19,1161,98],{}," ile workflow'u cron ifadesiyle periyodik çalıştırabilirsiniz. Saatler ",[616,1164,1165],{},"UTC","'dir:",[156,1168,1170],{"className":158,"code":1169,"language":160,"meta":161,"style":161},"on:\n  schedule:\n    - cron: '0 3 * * *'     # her gün 03:00 UTC (06:00 TR)\n",[19,1171,1172,1178,1185],{"__ignoreMap":161},[165,1173,1174,1176],{"class":167,"line":168},[165,1175,84],{"class":200},[165,1177,203],{"class":182},[165,1179,1180,1183],{"class":167,"line":175},[165,1181,1182],{"class":178},"  schedule",[165,1184,203],{"class":182},[165,1186,1187,1190,1193,1195,1198,1201,1204],{"class":167,"line":190},[165,1188,1189],{"class":182},"    -",[165,1191,1192],{"class":178}," cron",[165,1194,183],{"class":182},[165,1196,1197],{"class":182}," '",[165,1199,1200],{"class":186},"0 3 * * *",[165,1202,1203],{"class":182},"'",[165,1205,1206],{"class":171},"     # her gün 03:00 UTC (06:00 TR)\n",[15,1208,1209],{},"Gece testleri, bağımlılık taramaları ya da bu blogdaki gibi zamanlanmış içerik yayını için kullanışlıdır. Yoğun saatlerde birkaç dakika gecikmeyle başlayabileceğini unutmayın.",[10,1211,1213],{"id":1212},"_7-joblar-arası-bağımlılık-ve-artifact","7. Job'lar arası bağımlılık ve artifact",[15,1215,1216],{},"Build'i bir kez yapıp çıktıyı deploy job'una aktarmak için:",[156,1218,1220],{"className":158,"code":1219,"language":160,"meta":161,"style":161},"jobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\u002Fcheckout@v4\n      - run: npm ci && npm run build\n      - uses: actions\u002Fupload-artifact@v4\n        with:\n          name: dist\n          path: dist\u002F\n\n  deploy:\n    needs: build\n    if: github.ref == 'refs\u002Fheads\u002Fmain'\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\u002Fdownload-artifact@v4\n        with:\n          name: dist\n          path: dist\u002F\n      # ... rsync adımları\n",[19,1221,1222,1228,1234,1242,1248,1258,1269,1280,1286,1296,1306,1310,1316,1326,1336,1344,1350,1361,1367,1375,1383],{"__ignoreMap":161},[165,1223,1224,1226],{"class":167,"line":168},[165,1225,246],{"class":178},[165,1227,203],{"class":182},[165,1229,1230,1232],{"class":167,"line":175},[165,1231,254],{"class":178},[165,1233,203],{"class":182},[165,1235,1236,1238,1240],{"class":167,"line":190},[165,1237,262],{"class":178},[165,1239,183],{"class":182},[165,1241,267],{"class":186},[165,1243,1244,1246],{"class":167,"line":197},[165,1245,273],{"class":178},[165,1247,203],{"class":182},[165,1249,1250,1252,1254,1256],{"class":167,"line":206},[165,1251,281],{"class":182},[165,1253,284],{"class":178},[165,1255,183],{"class":182},[165,1257,289],{"class":186},[165,1259,1260,1262,1264,1266],{"class":167,"line":214},[165,1261,281],{"class":182},[165,1263,350],{"class":178},[165,1265,183],{"class":182},[165,1267,1268],{"class":186}," npm ci && npm run build\n",[165,1270,1271,1273,1275,1277],{"class":167,"line":230},[165,1272,281],{"class":182},[165,1274,284],{"class":178},[165,1276,183],{"class":182},[165,1278,1279],{"class":186}," actions\u002Fupload-artifact@v4\n",[165,1281,1282,1284],{"class":167,"line":238},[165,1283,312],{"class":178},[165,1285,203],{"class":182},[165,1287,1288,1291,1293],{"class":167,"line":243},[165,1289,1290],{"class":178},"          name",[165,1292,183],{"class":182},[165,1294,1295],{"class":186}," dist\n",[165,1297,1298,1301,1303],{"class":167,"line":251},[165,1299,1300],{"class":178},"          path",[165,1302,183],{"class":182},[165,1304,1305],{"class":186}," dist\u002F\n",[165,1307,1308],{"class":167,"line":259},[165,1309,194],{"emptyLinePlaceholder":193},[165,1311,1312,1314],{"class":167,"line":270},[165,1313,877],{"class":178},[165,1315,203],{"class":182},[165,1317,1318,1321,1323],{"class":167,"line":278},[165,1319,1320],{"class":178},"    needs",[165,1322,183],{"class":182},[165,1324,1325],{"class":186}," build\n",[165,1327,1328,1331,1333],{"class":167,"line":292},[165,1329,1330],{"class":178},"    if",[165,1332,183],{"class":182},[165,1334,1335],{"class":186}," github.ref == 'refs\u002Fheads\u002Fmain'\n",[165,1337,1338,1340,1342],{"class":167,"line":297},[165,1339,262],{"class":178},[165,1341,183],{"class":182},[165,1343,267],{"class":186},[165,1345,1346,1348],{"class":167,"line":309},[165,1347,273],{"class":178},[165,1349,203],{"class":182},[165,1351,1352,1354,1356,1358],{"class":167,"line":317},[165,1353,281],{"class":182},[165,1355,284],{"class":178},[165,1357,183],{"class":182},[165,1359,1360],{"class":186}," actions\u002Fdownload-artifact@v4\n",[165,1362,1363,1365],{"class":167,"line":329},[165,1364,312],{"class":178},[165,1366,203],{"class":182},[165,1368,1369,1371,1373],{"class":167,"line":340},[165,1370,1290],{"class":178},[165,1372,183],{"class":182},[165,1374,1295],{"class":186},[165,1376,1377,1379,1381],{"class":167,"line":345},[165,1378,1300],{"class":178},[165,1380,183],{"class":182},[165,1382,1305],{"class":186},[165,1384,1385],{"class":167,"line":358},[165,1386,1387],{"class":171},"      # ... rsync adımları\n",[10,1389,1391],{"id":1390},"_8-gh-cli-ile-workflow-yönetimi","8. gh CLI ile workflow yönetimi",[156,1393,1395],{"className":626,"code":1394,"language":628,"meta":161,"style":161},"gh workflow list\ngh workflow run deploy.yml            # workflow_dispatch tetikle\ngh run list --workflow=deploy.yml\ngh run watch                          # çalışan run'ı canlı izle\ngh run view --log-failed              # başarısız adımların logları\ngh run rerun --failed\n",[19,1396,1397,1406,1420,1432,1444,1459],{"__ignoreMap":161},[165,1398,1399,1401,1404],{"class":167,"line":168},[165,1400,622],{"class":635},[165,1402,1403],{"class":186}," workflow",[165,1405,702],{"class":186},[165,1407,1408,1410,1412,1414,1417],{"class":167,"line":175},[165,1409,622],{"class":635},[165,1411,1403],{"class":186},[165,1413,350],{"class":186},[165,1415,1416],{"class":186}," deploy.yml",[165,1418,1419],{"class":171},"            # workflow_dispatch tetikle\n",[165,1421,1422,1424,1426,1429],{"class":167,"line":190},[165,1423,622],{"class":635},[165,1425,350],{"class":186},[165,1427,1428],{"class":186}," list",[165,1430,1431],{"class":186}," --workflow=deploy.yml\n",[165,1433,1434,1436,1438,1441],{"class":167,"line":197},[165,1435,622],{"class":635},[165,1437,350],{"class":186},[165,1439,1440],{"class":186}," watch",[165,1442,1443],{"class":171},"                          # çalışan run'ı canlı izle\n",[165,1445,1446,1448,1450,1453,1456],{"class":167,"line":206},[165,1447,622],{"class":635},[165,1449,350],{"class":186},[165,1451,1452],{"class":186}," view",[165,1454,1455],{"class":186}," --log-failed",[165,1457,1458],{"class":171},"              # başarısız adımların logları\n",[165,1460,1461,1463,1465,1468],{"class":167,"line":214},[165,1462,622],{"class":635},[165,1464,350],{"class":186},[165,1466,1467],{"class":186}," rerun",[165,1469,1470],{"class":186}," --failed\n",[10,1472,1474],{"id":1473},"_9-sorun-giderme","9. Sorun giderme",[47,1476,1477,1487],{},[50,1478,1479],{},[53,1480,1481,1484],{},[56,1482,1483],{},"Sorun",[56,1485,1486],{},"Çözüm",[63,1488,1489,1503,1516,1524,1536],{},[53,1490,1491,1496],{},[68,1492,1493],{},[19,1494,1495],{},"Permission denied (publickey)",[68,1497,1498,1499,1502],{},"Secret'taki private key eksik\u002Fbozuk ya da public key ",[19,1500,1501],{},"authorized_keys","'te yok",[53,1504,1505,1510],{},[68,1506,1507],{},[19,1508,1509],{},"Host key verification failed",[68,1511,1512,1515],{},[19,1513,1514],{},"ssh-keyscan"," adımı eksik ya da yanlış host",[53,1517,1518,1521],{},[68,1519,1520],{},"Secret boş geliyor",[68,1522,1523],{},"Fork'tan açılan PR'larda secret verilmez; isim büyük\u002Fküçük harf duyarlıdır",[53,1525,1526,1529],{},[68,1527,1528],{},"Cache hiç tutmuyor",[68,1530,1531,1532,1535],{},"Lock dosyası (",[19,1533,1534],{},"package-lock.json",") repoda olmalı",[53,1537,1538,1541],{},[68,1539,1540],{},"Zamanlanmış workflow çalışmıyor",[68,1542,1543],{},"Sadece varsayılan branch'teki dosya geçerlidir; 60 gün aktivite olmayan repolarda otomatik devre dışı kalır",[10,1545,1547],{"id":1546},"sonuç","Sonuç",[15,1549,1550,1551,1553,1554,1557],{},"Birkaç düzine satırlık bir YAML ile her değişikliğin test edildiği ve ",[19,1552,37],{},"'e merge edildiği anda canlıya çıktığı bir akış kurmak mümkün. Başlangıç için tek bir ",[19,1555,1556],{},"ci.yml"," yeterli; deploy, matrix ve zamanlanmış görevleri ihtiyaç doğdukça ekleyebilirsiniz. Secret'ları repoya yazmamak ve deploy için ayrı, sınırlı yetkili bir SSH anahtarı kullanmak ise baştan uyulması gereken iki kuraldır.",[1559,1560,1561],"style",{},"html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}",{"title":161,"searchDepth":175,"depth":175,"links":1563},[1564,1565,1566,1567,1568,1569,1570,1571,1572,1573,1574],{"id":12,"depth":175,"text":13},{"id":44,"depth":175,"text":45},{"id":153,"depth":175,"text":154},{"id":469,"depth":175,"text":470},{"id":610,"depth":175,"text":611},{"id":776,"depth":175,"text":777},{"id":1156,"depth":175,"text":1157},{"id":1212,"depth":175,"text":1213},{"id":1390,"depth":175,"text":1391},{"id":1473,"depth":175,"text":1474},{"id":1546,"depth":175,"text":1547},"2026-10-29","Bir projeyi her push'ta build edip test eden, main'e merge edilince SSH ile kendi sunucunuza deploy eden GitHub Actions workflow'u adım adım.","md",{},"\u002Ftr\u002Fblog\u002Fgithub-actions-ci-cd",{"title":5,"description":1576},"tr\u002Fblog\u002Fgithub-actions-ci-cd",[1583,1584,1585,1586],"github","ci-cd","devops","rehber","\u002Fen\u002Fblog\u002Fgithub-actions-ci-cd","EN4bXsyHELDqCBu4GDlv80SJJVc2_YDJDvIewUEmhrs",[1590,1598,1605],{"path":1591,"title":1592,"description":1593,"date":1594,"tags":1595},"\u002Ftr\u002Fblog\u002Fnginx-reverse-proxy-ssl","Nginx Reverse Proxy ve Let's Encrypt SSL Kurulumu","Bir Node.js ya da Spring Boot uygulamasını Nginx arkasına almak, Certbot ile ücretsiz SSL sertifikası almak ve otomatik yenilemeyi kurmak.","2026-10-28",[1596,1597,1585,1586],"nginx","linux",{"path":1599,"title":1600,"description":1601,"date":1602,"tags":1603},"\u002Ftr\u002Fblog\u002Fdocker-compose-guide","Docker Compose ile Çoklu Servis Çalıştırma","Uygulama, veritabanı ve cache'i tek bir compose.yaml ile ayağa kaldırmak — servisler, network, volume, healthcheck, env dosyaları ve sık kullanılan komutlar.","2026-10-27",[1604,1585,1586],"docker",{"path":1606,"title":1607,"description":1608,"date":1609,"tags":1610},"\u002Ftr\u002Fblog\u002Fkubectl-commands","Sık Kullanılan kubectl Komutları","Kubernetes ile günlük işin döndüğü kubectl komutları — context, pod, deployment, log, exec, port-forward, rollout ve sorun giderme.","2026-10-26",[1611,1585,1586],"kubernetes",1793234957825]