[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"content-/blog/2023/09/09/continuence-integration":3,"related-/blog/2023/09/09/continuence-integration":2030},{"id":4,"title":5,"author":6,"body":7,"cover":2013,"date":2014,"description":13,"draft":2015,"excerpt":2016,"extension":2017,"github":23,"keywords":2018,"meta":2019,"navigation":90,"path":2020,"seo":2021,"shortDesc":2016,"slug":2022,"stem":2023,"tags":2024,"video":2016,"__hash__":2029},"blog/blog/2023/09/09/continuence-integration.md","Continuences Integration","Oz Shemesh",{"type":8,"value":9,"toc":2002},"minimark",[10,14,27,32,41,58,62,65,68,166,170,173,189,882,887,902,917,924,928,931,936,1033,1036,1042,1045,1467,1471,1474,1485,1500,1596,1600,1603,1617,1954,1958,1966,1969,1998],[11,12,13],"p",{},"In the forthcoming blog post, we'll explore the deployment of a Nuxt 3 (Vue 3) application running on a Node.js environment. Our focus will be on setting up a comprehensive CI pipeline, encompassing tasks such as testing, building, Docker containerization, and pushing an image to a container registry.",[15,16,18],"info-box",{"type":17},"",[11,19,20],{},[21,22,26],"a",{"href":23,"rel":24},"https://github.com/devozs/blog-frontend",[25],"nofollow","Show me the Code!",[28,29,31],"h2",{"id":30},"guidelines","Guidelines",[11,33,34,35,40],{},"To achieve this, I've used ",[21,36,39],{"href":37,"rel":38},"https://github.com/features/actions",[25],"GitHub Actions"," as the CI workflow. Nevertheless, it's important to highlight that you have the flexibility to opt for different tools according to your requirements, whether it's Jenkins, Gitlab CI, or any other CI solution that aligns with your preferences.",[15,42,44,47],{"type":43},"warning",[11,45,46],{},"Container Registry Prepeartion!",[48,49,50],"template",{"v-slot:details":17},[11,51,52,53],{},"I've chosen to use GitHub Packages as the container registry to store the application image. However, it's essential to note that you have the flexibility to opt for alternative container registries such as Docker Hub, Azure Container Registry, and others if you prefer.",[21,54,57],{"href":55,"rel":56},"https://github.com/features/packages",[25],"More information can be found here",[28,59,61],{"id":60},"dockerfile","Dockerfile",[11,63,64],{},"As previously mentioned, we're working with a Node.js application, which is why we're using the node:17 parent image.",[11,66,67],{},"In the following lines, we'll be copying the necessary files into the container, exposing a container port, and configuring the entry point to run the server.",[69,70,74],"pre",{"className":71,"code":72,"language":73,"meta":17,"style":17},"language-docker shiki shiki-themes nord github-dark monokai","FROM node:17-alpine\n\nRUN mkdir -p /usr/src/nuxt-app\nWORKDIR /usr/src/nuxt-app\nCOPY . .\n\nRUN npm ci && npm cache clean --force\nRUN npm run build\n\nENV NUXT_HOST=0.0.0.0\nENV NUXT_PORT=3000\n\nEXPOSE 3000\n\nENTRYPOINT [\"node\", \".output/server/index.mjs\"]\n","docker",[75,76,77,85,92,98,104,110,115,121,127,132,138,144,149,155,160],"code",{"__ignoreMap":17},[78,79,82],"span",{"class":80,"line":81},"line",1,[78,83,84],{},"FROM node:17-alpine\n",[78,86,88],{"class":80,"line":87},2,[78,89,91],{"emptyLinePlaceholder":90},true,"\n",[78,93,95],{"class":80,"line":94},3,[78,96,97],{},"RUN mkdir -p /usr/src/nuxt-app\n",[78,99,101],{"class":80,"line":100},4,[78,102,103],{},"WORKDIR /usr/src/nuxt-app\n",[78,105,107],{"class":80,"line":106},5,[78,108,109],{},"COPY . .\n",[78,111,113],{"class":80,"line":112},6,[78,114,91],{"emptyLinePlaceholder":90},[78,116,118],{"class":80,"line":117},7,[78,119,120],{},"RUN npm ci && npm cache clean --force\n",[78,122,124],{"class":80,"line":123},8,[78,125,126],{},"RUN npm run build\n",[78,128,130],{"class":80,"line":129},9,[78,131,91],{"emptyLinePlaceholder":90},[78,133,135],{"class":80,"line":134},10,[78,136,137],{},"ENV NUXT_HOST=0.0.0.0\n",[78,139,141],{"class":80,"line":140},11,[78,142,143],{},"ENV NUXT_PORT=3000\n",[78,145,147],{"class":80,"line":146},12,[78,148,91],{"emptyLinePlaceholder":90},[78,150,152],{"class":80,"line":151},13,[78,153,154],{},"EXPOSE 3000\n",[78,156,158],{"class":80,"line":157},14,[78,159,91],{"emptyLinePlaceholder":90},[78,161,163],{"class":80,"line":162},15,[78,164,165],{},"ENTRYPOINT [\"node\", \".output/server/index.mjs\"]\n",[28,167,169],{"id":168},"ci-workflow","CI Workflow",[11,171,172],{},"We are using GitHub Action for the CI pipeline implementation.",[11,174,175,176,183,184],{},"GitHub search workflow yamls under the repository location: ",[177,178,179],"em",{},[180,181,182],"strong",{},".github/workflows",".\nWe've created a workflow named ",[177,185,186],{},[180,187,188],{},"buildAndPush.yaml",[15,190,191,194],{"type":75},[11,192,193],{},"Full buildAndPush.yaml",[48,195,196],{"v-slot:details":17},[69,197,201],{"className":198,"code":199,"language":200,"meta":17,"style":17},"language-yaml shiki shiki-themes nord github-dark monokai","name: Blog Frontend - Test, Build and Push\n\non:\n  workflow_dispatch:\n  push:\n    branches:\n      - 'main'\njobs:\n  test:\n    runs-on: ubuntu-latest\n    \n    permissions:\n      contents: read\n      pull-requests: write\n\n    steps:\n    - uses: actions/checkout@v3\n\n    - name: 'Install Node'\n      uses: actions/setup-node@v3\n      with:\n        node-version: '17'\n\n    - name: 'Install Deps'\n      run: |\n        npm install\n        npm run build\n\n    - name: 'Test'\n      run: npx vitest --coverage\n\n    - name: 'Report Coverage'\n      if: always() # Also generate the report if tests are failing\n      uses:  davelosert/vitest-coverage-report-action@v2\n\n  build-push:\n    needs: test\n    env:\n      REGISTRY: ghcr.io/devozs\n      IMAGE: blog-frontend\n    runs-on: ubuntu-latest\n    steps:\n      - name: \"Checkout GitHub Action\"\n        uses: actions/checkout@v3\n\n      - name: Generate build ID\n        id: prep\n        run: |\n            branch=${GITHUB_REF##*/}\n            sha=${GITHUB_SHA::8}\n            ts=$(date +%s)\n            echo \"BUILD_ID=${branch}-${sha}-${ts}\" >> $GITHUB_OUTPUT\n      - name: Set up QEMU\n        uses: docker/setup-qemu-action@v2\n\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@v2\n\n      - name: \"Build\"\n        run: |\n          docker build -f Dockerfile . -t ${{ env.IMAGE }}:${{ steps.prep.outputs.BUILD_ID }}\n\n      - name: Log in to the Container registry\n        uses: docker/login-action@v2\n        with:\n          registry: ghcr.io\n          username: ${{ github.actor }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: \"Docker Push\"\n        run: |\n          docker tag ${{ env.IMAGE }}:${{ steps.prep.outputs.BUILD_ID }} ${{ env.REGISTRY }}/${{ env.IMAGE }}:${{ steps.prep.outputs.BUILD_ID }}\n          docker push ${{ env.REGISTRY }}/${{ env.IMAGE }}:${{ steps.prep.outputs.BUILD_ID }}\n","yaml",[75,202,203,217,221,230,237,244,251,266,273,280,290,296,303,313,323,327,335,349,354,371,382,390,405,410,426,438,444,450,455,471,481,486,502,517,527,532,540,551,559,570,581,590,597,615,625,630,642,653,663,669,675,681,687,699,709,714,726,736,741,757,766,772,777,789,799,807,818,829,840,845,861,870,876],{"__ignoreMap":17},[78,204,205,209,213],{"class":80,"line":81},[78,206,208],{"class":207},"skFRX","name",[78,210,212],{"class":211},"sUaCP",":",[78,214,216],{"class":215},"siq7d"," Blog Frontend - Test, Build and Push\n",[78,218,219],{"class":80,"line":87},[78,220,91],{"emptyLinePlaceholder":90},[78,222,223,227],{"class":80,"line":94},[78,224,226],{"class":225},"sKfHY","on",[78,228,229],{"class":211},":\n",[78,231,232,235],{"class":80,"line":100},[78,233,234],{"class":207},"  workflow_dispatch",[78,236,229],{"class":211},[78,238,239,242],{"class":80,"line":106},[78,240,241],{"class":207},"  push",[78,243,229],{"class":211},[78,245,246,249],{"class":80,"line":112},[78,247,248],{"class":207},"    branches",[78,250,229],{"class":211},[78,252,253,256,260,263],{"class":80,"line":117},[78,254,255],{"class":211},"      -",[78,257,259],{"class":258},"sQE_P"," '",[78,261,262],{"class":215},"main",[78,264,265],{"class":258},"'\n",[78,267,268,271],{"class":80,"line":123},[78,269,270],{"class":207},"jobs",[78,272,229],{"class":211},[78,274,275,278],{"class":80,"line":129},[78,276,277],{"class":207},"  test",[78,279,229],{"class":211},[78,281,282,285,287],{"class":80,"line":134},[78,283,284],{"class":207},"    runs-on",[78,286,212],{"class":211},[78,288,289],{"class":215}," ubuntu-latest\n",[78,291,292],{"class":80,"line":140},[78,293,295],{"class":294},"sw3Zv","    \n",[78,297,298,301],{"class":80,"line":146},[78,299,300],{"class":207},"    permissions",[78,302,229],{"class":211},[78,304,305,308,310],{"class":80,"line":151},[78,306,307],{"class":207},"      contents",[78,309,212],{"class":211},[78,311,312],{"class":215}," read\n",[78,314,315,318,320],{"class":80,"line":157},[78,316,317],{"class":207},"      pull-requests",[78,319,212],{"class":211},[78,321,322],{"class":215}," write\n",[78,324,325],{"class":80,"line":162},[78,326,91],{"emptyLinePlaceholder":90},[78,328,330,333],{"class":80,"line":329},16,[78,331,332],{"class":207},"    steps",[78,334,229],{"class":211},[78,336,338,341,344,346],{"class":80,"line":337},17,[78,339,340],{"class":211},"    -",[78,342,343],{"class":207}," uses",[78,345,212],{"class":211},[78,347,348],{"class":215}," actions/checkout@v3\n",[78,350,352],{"class":80,"line":351},18,[78,353,91],{"emptyLinePlaceholder":90},[78,355,357,359,362,364,366,369],{"class":80,"line":356},19,[78,358,340],{"class":211},[78,360,361],{"class":207}," name",[78,363,212],{"class":211},[78,365,259],{"class":258},[78,367,368],{"class":215},"Install Node",[78,370,265],{"class":258},[78,372,374,377,379],{"class":80,"line":373},20,[78,375,376],{"class":207},"      uses",[78,378,212],{"class":211},[78,380,381],{"class":215}," actions/setup-node@v3\n",[78,383,385,388],{"class":80,"line":384},21,[78,386,387],{"class":207},"      with",[78,389,229],{"class":211},[78,391,393,396,398,400,403],{"class":80,"line":392},22,[78,394,395],{"class":207},"        node-version",[78,397,212],{"class":211},[78,399,259],{"class":258},[78,401,402],{"class":215},"17",[78,404,265],{"class":258},[78,406,408],{"class":80,"line":407},23,[78,409,91],{"emptyLinePlaceholder":90},[78,411,413,415,417,419,421,424],{"class":80,"line":412},24,[78,414,340],{"class":211},[78,416,361],{"class":207},[78,418,212],{"class":211},[78,420,259],{"class":258},[78,422,423],{"class":215},"Install Deps",[78,425,265],{"class":258},[78,427,429,432,434],{"class":80,"line":428},25,[78,430,431],{"class":207},"      run",[78,433,212],{"class":211},[78,435,437],{"class":436},"s4BcI"," |\n",[78,439,441],{"class":80,"line":440},26,[78,442,443],{"class":215},"        npm install\n",[78,445,447],{"class":80,"line":446},27,[78,448,449],{"class":215},"        npm run build\n",[78,451,453],{"class":80,"line":452},28,[78,454,91],{"emptyLinePlaceholder":90},[78,456,458,460,462,464,466,469],{"class":80,"line":457},29,[78,459,340],{"class":211},[78,461,361],{"class":207},[78,463,212],{"class":211},[78,465,259],{"class":258},[78,467,468],{"class":215},"Test",[78,470,265],{"class":258},[78,472,474,476,478],{"class":80,"line":473},30,[78,475,431],{"class":207},[78,477,212],{"class":211},[78,479,480],{"class":215}," npx vitest --coverage\n",[78,482,484],{"class":80,"line":483},31,[78,485,91],{"emptyLinePlaceholder":90},[78,487,489,491,493,495,497,500],{"class":80,"line":488},32,[78,490,340],{"class":211},[78,492,361],{"class":207},[78,494,212],{"class":211},[78,496,259],{"class":258},[78,498,499],{"class":215},"Report Coverage",[78,501,265],{"class":258},[78,503,505,508,510,513],{"class":80,"line":504},33,[78,506,507],{"class":207},"      if",[78,509,212],{"class":211},[78,511,512],{"class":215}," always()",[78,514,516],{"class":515},"sr5Cr"," # Also generate the report if tests are failing\n",[78,518,520,522,524],{"class":80,"line":519},34,[78,521,376],{"class":207},[78,523,212],{"class":211},[78,525,526],{"class":215},"  davelosert/vitest-coverage-report-action@v2\n",[78,528,530],{"class":80,"line":529},35,[78,531,91],{"emptyLinePlaceholder":90},[78,533,535,538],{"class":80,"line":534},36,[78,536,537],{"class":207},"  build-push",[78,539,229],{"class":211},[78,541,543,546,548],{"class":80,"line":542},37,[78,544,545],{"class":207},"    needs",[78,547,212],{"class":211},[78,549,550],{"class":215}," test\n",[78,552,554,557],{"class":80,"line":553},38,[78,555,556],{"class":207},"    env",[78,558,229],{"class":211},[78,560,562,565,567],{"class":80,"line":561},39,[78,563,564],{"class":207},"      REGISTRY",[78,566,212],{"class":211},[78,568,569],{"class":215}," ghcr.io/devozs\n",[78,571,573,576,578],{"class":80,"line":572},40,[78,574,575],{"class":207},"      IMAGE",[78,577,212],{"class":211},[78,579,580],{"class":215}," blog-frontend\n",[78,582,584,586,588],{"class":80,"line":583},41,[78,585,284],{"class":207},[78,587,212],{"class":211},[78,589,289],{"class":215},[78,591,593,595],{"class":80,"line":592},42,[78,594,332],{"class":207},[78,596,229],{"class":211},[78,598,600,602,604,606,609,612],{"class":80,"line":599},43,[78,601,255],{"class":211},[78,603,361],{"class":207},[78,605,212],{"class":211},[78,607,608],{"class":258}," \"",[78,610,611],{"class":215},"Checkout GitHub Action",[78,613,614],{"class":258},"\"\n",[78,616,618,621,623],{"class":80,"line":617},44,[78,619,620],{"class":207},"        uses",[78,622,212],{"class":211},[78,624,348],{"class":215},[78,626,628],{"class":80,"line":627},45,[78,629,91],{"emptyLinePlaceholder":90},[78,631,633,635,637,639],{"class":80,"line":632},46,[78,634,255],{"class":211},[78,636,361],{"class":207},[78,638,212],{"class":211},[78,640,641],{"class":215}," Generate build ID\n",[78,643,645,648,650],{"class":80,"line":644},47,[78,646,647],{"class":207},"        id",[78,649,212],{"class":211},[78,651,652],{"class":215}," prep\n",[78,654,656,659,661],{"class":80,"line":655},48,[78,657,658],{"class":207},"        run",[78,660,212],{"class":211},[78,662,437],{"class":436},[78,664,666],{"class":80,"line":665},49,[78,667,668],{"class":215},"            branch=${GITHUB_REF##*/}\n",[78,670,672],{"class":80,"line":671},50,[78,673,674],{"class":215},"            sha=${GITHUB_SHA::8}\n",[78,676,678],{"class":80,"line":677},51,[78,679,680],{"class":215},"            ts=$(date +%s)\n",[78,682,684],{"class":80,"line":683},52,[78,685,686],{"class":215},"            echo \"BUILD_ID=${branch}-${sha}-${ts}\" >> $GITHUB_OUTPUT\n",[78,688,690,692,694,696],{"class":80,"line":689},53,[78,691,255],{"class":211},[78,693,361],{"class":207},[78,695,212],{"class":211},[78,697,698],{"class":215}," Set up QEMU\n",[78,700,702,704,706],{"class":80,"line":701},54,[78,703,620],{"class":207},[78,705,212],{"class":211},[78,707,708],{"class":215}," docker/setup-qemu-action@v2\n",[78,710,712],{"class":80,"line":711},55,[78,713,91],{"emptyLinePlaceholder":90},[78,715,717,719,721,723],{"class":80,"line":716},56,[78,718,255],{"class":211},[78,720,361],{"class":207},[78,722,212],{"class":211},[78,724,725],{"class":215}," Set up Docker Buildx\n",[78,727,729,731,733],{"class":80,"line":728},57,[78,730,620],{"class":207},[78,732,212],{"class":211},[78,734,735],{"class":215}," docker/setup-buildx-action@v2\n",[78,737,739],{"class":80,"line":738},58,[78,740,91],{"emptyLinePlaceholder":90},[78,742,744,746,748,750,752,755],{"class":80,"line":743},59,[78,745,255],{"class":211},[78,747,361],{"class":207},[78,749,212],{"class":211},[78,751,608],{"class":258},[78,753,754],{"class":215},"Build",[78,756,614],{"class":258},[78,758,760,762,764],{"class":80,"line":759},60,[78,761,658],{"class":207},[78,763,212],{"class":211},[78,765,437],{"class":436},[78,767,769],{"class":80,"line":768},61,[78,770,771],{"class":215},"          docker build -f Dockerfile . -t ${{ env.IMAGE }}:${{ steps.prep.outputs.BUILD_ID }}\n",[78,773,775],{"class":80,"line":774},62,[78,776,91],{"emptyLinePlaceholder":90},[78,778,780,782,784,786],{"class":80,"line":779},63,[78,781,255],{"class":211},[78,783,361],{"class":207},[78,785,212],{"class":211},[78,787,788],{"class":215}," Log in to the Container registry\n",[78,790,792,794,796],{"class":80,"line":791},64,[78,793,620],{"class":207},[78,795,212],{"class":211},[78,797,798],{"class":215}," docker/login-action@v2\n",[78,800,802,805],{"class":80,"line":801},65,[78,803,804],{"class":207},"        with",[78,806,229],{"class":211},[78,808,810,813,815],{"class":80,"line":809},66,[78,811,812],{"class":207},"          registry",[78,814,212],{"class":211},[78,816,817],{"class":215}," ghcr.io\n",[78,819,821,824,826],{"class":80,"line":820},67,[78,822,823],{"class":207},"          username",[78,825,212],{"class":211},[78,827,828],{"class":215}," ${{ github.actor }}\n",[78,830,832,835,837],{"class":80,"line":831},68,[78,833,834],{"class":207},"          password",[78,836,212],{"class":211},[78,838,839],{"class":215}," ${{ secrets.GITHUB_TOKEN }}\n",[78,841,843],{"class":80,"line":842},69,[78,844,91],{"emptyLinePlaceholder":90},[78,846,848,850,852,854,856,859],{"class":80,"line":847},70,[78,849,255],{"class":211},[78,851,361],{"class":207},[78,853,212],{"class":211},[78,855,608],{"class":258},[78,857,858],{"class":215},"Docker Push",[78,860,614],{"class":258},[78,862,864,866,868],{"class":80,"line":863},71,[78,865,658],{"class":207},[78,867,212],{"class":211},[78,869,437],{"class":436},[78,871,873],{"class":80,"line":872},72,[78,874,875],{"class":215},"          docker tag ${{ env.IMAGE }}:${{ steps.prep.outputs.BUILD_ID }} ${{ env.REGISTRY }}/${{ env.IMAGE }}:${{ steps.prep.outputs.BUILD_ID }}\n",[78,877,879],{"class":80,"line":878},73,[78,880,881],{"class":215},"          docker push ${{ env.REGISTRY }}/${{ env.IMAGE }}:${{ steps.prep.outputs.BUILD_ID }}\n",[883,884,886],"h3",{"id":885},"setup-action","Setup Action",[11,888,889,890,895,896,901],{},"We've breaken our pipline into two main parts (jobs in GitHub Action terminology): ",[177,891,892],{},[180,893,894],{},"test"," and ",[177,897,898],{},[180,899,900],{},"build-push",".",[903,904,905,909],"ul",{},[906,907,908],"li",{},"test: We've implemented simple unit and component tests in this example. These tests are included to demonstrate where and how to place tests before proceeding to the build phase.",[906,910,911,912],{},"build-push: In this phase, we build the container using the Dockerfile described in the previous section and push it to the GitHub registry. This job stars running only after a succesful complition of test phase.\n",[913,914],"img",{"alt":915,"src":916,"title":915},"GitHub Action Summary","/images/blog/2023/09/08/github-action-diagram.png",[11,918,919,920],{},"You can view list workflows execution\n",[913,921],{"alt":922,"src":923,"title":922},"GitHub Action List","/images/blog/2023/09/08/github-action-list.png",[883,925,927],{"id":926},"unit-compoenent-tests","Unit / Compoenent Tests",[11,929,930],{},"Basic testing implementation helps demonstrate a separate phase before triggering the build-push phase. This prevents images from being pushed into the registry in case the tests fail or are invalid.",[15,932,933],{"type":43},[11,934,935],{},"This is just an example; in a real-case scenario, there should be many more meaningful funtional and non-functional tests!",[69,937,939],{"className":198,"code":938,"language":200,"meta":17,"style":17},"    - name: 'Install Deps'\n      run: |\n        npm install\n        npm run build\n\n    - name: 'Test'\n      run: npx vitest --coverage\n\n    - name: 'Report Coverage'\n      if: always() # Also generate the report if tests are failing\n      uses:  davelosert/vitest-coverage-report-action@v2\n",[75,940,941,955,963,967,971,975,989,997,1001,1015,1025],{"__ignoreMap":17},[78,942,943,945,947,949,951,953],{"class":80,"line":81},[78,944,340],{"class":211},[78,946,361],{"class":207},[78,948,212],{"class":211},[78,950,259],{"class":258},[78,952,423],{"class":215},[78,954,265],{"class":258},[78,956,957,959,961],{"class":80,"line":87},[78,958,431],{"class":207},[78,960,212],{"class":211},[78,962,437],{"class":436},[78,964,965],{"class":80,"line":94},[78,966,443],{"class":215},[78,968,969],{"class":80,"line":100},[78,970,449],{"class":215},[78,972,973],{"class":80,"line":106},[78,974,91],{"emptyLinePlaceholder":90},[78,976,977,979,981,983,985,987],{"class":80,"line":112},[78,978,340],{"class":211},[78,980,361],{"class":207},[78,982,212],{"class":211},[78,984,259],{"class":258},[78,986,468],{"class":215},[78,988,265],{"class":258},[78,990,991,993,995],{"class":80,"line":117},[78,992,431],{"class":207},[78,994,212],{"class":211},[78,996,480],{"class":215},[78,998,999],{"class":80,"line":123},[78,1000,91],{"emptyLinePlaceholder":90},[78,1002,1003,1005,1007,1009,1011,1013],{"class":80,"line":129},[78,1004,340],{"class":211},[78,1006,361],{"class":207},[78,1008,212],{"class":211},[78,1010,259],{"class":258},[78,1012,499],{"class":215},[78,1014,265],{"class":258},[78,1016,1017,1019,1021,1023],{"class":80,"line":134},[78,1018,507],{"class":207},[78,1020,212],{"class":211},[78,1022,512],{"class":215},[78,1024,516],{"class":515},[78,1026,1027,1029,1031],{"class":80,"line":140},[78,1028,376],{"class":207},[78,1030,212],{"class":211},[78,1032,526],{"class":215},[11,1034,1035],{},"Tests results on GitHub Actions:",[11,1037,1038],{},[913,1039],{"alt":1040,"src":1041,"title":1040},"GitHub Action Tests","/images/blog/2023/09/09/github-actions-tests.png",[11,1043,1044],{},"Running tests locally:",[69,1046,1050],{"className":1047,"code":1048,"language":1049,"meta":17,"style":17},"language-bash shiki shiki-themes nord github-dark monokai","npm run coverage\n\n> coverage\n> vitest run --coverage\n\n\n RUN  v0.34.4 /home/devozs/workspace/sandbox/blog-frontend\n      Coverage enabled with v8\n\n ✓ tests/imports.test.ts (2)\n   ✓ import vue components (2)\n     ✓ normal imports as expected\n     ✓ dynamic imports as expected\n\n Test Files  1 passed (1)\n      Tests  2 passed (2)\n   Start at  00:22:38\n   Duration  1.03s (transform 83ms, setup 0ms, collect 21ms, tests 279ms, environment 0ms, prepare 89ms)\n\n % Coverage report from v8\n-------------|---------|----------|---------|---------|-------------------\nFile         | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s \n-------------|---------|----------|---------|---------|-------------------\nAll files    |     100 |      100 |     100 |     100 |                   \n InfoBox.vue |     100 |      100 |     100 |     100 |                   \n-------------|---------|----------|---------|---------|-------------------\n\n","bash",[75,1051,1052,1064,1068,1075,1082,1086,1090,1101,1115,1119,1130,1146,1163,1176,1180,1198,1210,1221,1267,1271,1287,1316,1362,1386,1418,1443],{"__ignoreMap":17},[78,1053,1054,1058,1061],{"class":80,"line":81},[78,1055,1057],{"class":1056},"sNHwn","npm",[78,1059,1060],{"class":215}," run",[78,1062,1063],{"class":215}," coverage\n",[78,1065,1066],{"class":80,"line":87},[78,1067,91],{"emptyLinePlaceholder":90},[78,1069,1070,1073],{"class":80,"line":94},[78,1071,1072],{"class":436},">",[78,1074,1063],{"class":294},[78,1076,1077,1079],{"class":80,"line":100},[78,1078,1072],{"class":436},[78,1080,1081],{"class":294}," vitest run --coverage\n",[78,1083,1084],{"class":80,"line":106},[78,1085,91],{"emptyLinePlaceholder":90},[78,1087,1088],{"class":80,"line":112},[78,1089,91],{"emptyLinePlaceholder":90},[78,1091,1092,1095,1098],{"class":80,"line":117},[78,1093,1094],{"class":1056}," RUN",[78,1096,1097],{"class":215},"  v0.34.4",[78,1099,1100],{"class":215}," /home/devozs/workspace/sandbox/blog-frontend\n",[78,1102,1103,1106,1109,1112],{"class":80,"line":123},[78,1104,1105],{"class":1056},"      Coverage",[78,1107,1108],{"class":215}," enabled",[78,1110,1111],{"class":215}," with",[78,1113,1114],{"class":215}," v8\n",[78,1116,1117],{"class":80,"line":129},[78,1118,91],{"emptyLinePlaceholder":90},[78,1120,1121,1124,1127],{"class":80,"line":134},[78,1122,1123],{"class":1056}," ✓",[78,1125,1126],{"class":215}," tests/imports.test.ts",[78,1128,1129],{"class":294}," (2)\n",[78,1131,1132,1135,1138,1141,1144],{"class":80,"line":140},[78,1133,1134],{"class":1056},"   ✓",[78,1136,1137],{"class":215}," import",[78,1139,1140],{"class":215}," vue",[78,1142,1143],{"class":215}," components",[78,1145,1129],{"class":294},[78,1147,1148,1151,1154,1157,1160],{"class":80,"line":146},[78,1149,1150],{"class":1056},"     ✓",[78,1152,1153],{"class":215}," normal",[78,1155,1156],{"class":215}," imports",[78,1158,1159],{"class":215}," as",[78,1161,1162],{"class":215}," expected\n",[78,1164,1165,1167,1170,1172,1174],{"class":80,"line":151},[78,1166,1150],{"class":1056},[78,1168,1169],{"class":215}," dynamic",[78,1171,1156],{"class":215},[78,1173,1159],{"class":215},[78,1175,1162],{"class":215},[78,1177,1178],{"class":80,"line":157},[78,1179,91],{"emptyLinePlaceholder":90},[78,1181,1182,1185,1188,1192,1195],{"class":80,"line":162},[78,1183,1184],{"class":1056}," Test",[78,1186,1187],{"class":215}," Files",[78,1189,1191],{"class":1190},"sX_qU","  1",[78,1193,1194],{"class":215}," passed",[78,1196,1197],{"class":294}," (1)\n",[78,1199,1200,1203,1206,1208],{"class":80,"line":329},[78,1201,1202],{"class":1056},"      Tests",[78,1204,1205],{"class":1190},"  2",[78,1207,1194],{"class":215},[78,1209,1129],{"class":294},[78,1211,1212,1215,1218],{"class":80,"line":337},[78,1213,1214],{"class":1056},"   Start",[78,1216,1217],{"class":215}," at",[78,1219,1220],{"class":215},"  00:22:38\n",[78,1222,1223,1226,1229,1232,1235,1238,1241,1244,1247,1250,1253,1256,1258,1261,1264],{"class":80,"line":351},[78,1224,1225],{"class":1056},"   Duration",[78,1227,1228],{"class":215},"  1.03s",[78,1230,1231],{"class":294}," (transform ",[78,1233,1234],{"class":215},"83ms,",[78,1236,1237],{"class":215}," setup",[78,1239,1240],{"class":215}," 0ms,",[78,1242,1243],{"class":215}," collect",[78,1245,1246],{"class":215}," 21ms,",[78,1248,1249],{"class":215}," tests",[78,1251,1252],{"class":215}," 279ms,",[78,1254,1255],{"class":215}," environment",[78,1257,1240],{"class":215},[78,1259,1260],{"class":215}," prepare",[78,1262,1263],{"class":215}," 89ms",[78,1265,1266],{"class":294},")\n",[78,1268,1269],{"class":80,"line":356},[78,1270,91],{"emptyLinePlaceholder":90},[78,1272,1273,1276,1279,1282,1285],{"class":80,"line":373},[78,1274,1275],{"class":1056}," %",[78,1277,1278],{"class":215}," Coverage",[78,1280,1281],{"class":215}," report",[78,1283,1284],{"class":215}," from",[78,1286,1114],{"class":215},[78,1288,1289,1292,1295,1298,1300,1303,1305,1307,1309,1311,1313],{"class":80,"line":384},[78,1290,1291],{"class":1056},"-------------",[78,1293,1294],{"class":436},"|",[78,1296,1297],{"class":1056},"---------",[78,1299,1294],{"class":436},[78,1301,1302],{"class":1056},"----------",[78,1304,1294],{"class":436},[78,1306,1297],{"class":1056},[78,1308,1294],{"class":436},[78,1310,1297],{"class":1056},[78,1312,1294],{"class":436},[78,1314,1315],{"class":1056},"-------------------\n",[78,1317,1318,1321,1324,1326,1329,1332,1334,1337,1339,1341,1344,1346,1348,1351,1353,1356,1359],{"class":80,"line":392},[78,1319,1320],{"class":1056},"File",[78,1322,1323],{"class":436},"         |",[78,1325,1275],{"class":1056},[78,1327,1328],{"class":215}," Stmts",[78,1330,1331],{"class":436}," |",[78,1333,1275],{"class":1056},[78,1335,1336],{"class":215}," Branch",[78,1338,1331],{"class":436},[78,1340,1275],{"class":1056},[78,1342,1343],{"class":215}," Funcs",[78,1345,1331],{"class":436},[78,1347,1275],{"class":1056},[78,1349,1350],{"class":215}," Lines",[78,1352,1331],{"class":436},[78,1354,1355],{"class":1056}," Uncovered",[78,1357,1358],{"class":215}," Line",[78,1360,1361],{"class":515}," #s \n",[78,1363,1364,1366,1368,1370,1372,1374,1376,1378,1380,1382,1384],{"class":80,"line":407},[78,1365,1291],{"class":1056},[78,1367,1294],{"class":436},[78,1369,1297],{"class":1056},[78,1371,1294],{"class":436},[78,1373,1302],{"class":1056},[78,1375,1294],{"class":436},[78,1377,1297],{"class":1056},[78,1379,1294],{"class":436},[78,1381,1297],{"class":1056},[78,1383,1294],{"class":436},[78,1385,1315],{"class":1056},[78,1387,1388,1391,1394,1397,1400,1402,1405,1407,1409,1411,1413,1415],{"class":80,"line":412},[78,1389,1390],{"class":1056},"All",[78,1392,1393],{"class":215}," files",[78,1395,1396],{"class":436},"    |",[78,1398,1399],{"class":1056},"     100",[78,1401,1331],{"class":436},[78,1403,1404],{"class":1056},"      100",[78,1406,1331],{"class":436},[78,1408,1399],{"class":1056},[78,1410,1331],{"class":436},[78,1412,1399],{"class":1056},[78,1414,1331],{"class":436},[78,1416,1417],{"class":294},"                   \n",[78,1419,1420,1423,1425,1427,1429,1431,1433,1435,1437,1439,1441],{"class":80,"line":428},[78,1421,1422],{"class":1056}," InfoBox.vue",[78,1424,1331],{"class":436},[78,1426,1399],{"class":1056},[78,1428,1331],{"class":436},[78,1430,1404],{"class":1056},[78,1432,1331],{"class":436},[78,1434,1399],{"class":1056},[78,1436,1331],{"class":436},[78,1438,1399],{"class":1056},[78,1440,1331],{"class":436},[78,1442,1417],{"class":294},[78,1444,1445,1447,1449,1451,1453,1455,1457,1459,1461,1463,1465],{"class":80,"line":440},[78,1446,1291],{"class":1056},[78,1448,1294],{"class":436},[78,1450,1297],{"class":1056},[78,1452,1294],{"class":436},[78,1454,1302],{"class":1056},[78,1456,1294],{"class":436},[78,1458,1297],{"class":1056},[78,1460,1294],{"class":436},[78,1462,1297],{"class":1056},[78,1464,1294],{"class":436},[78,1466,1315],{"class":1056},[883,1468,1470],{"id":1469},"build-docker","Build Docker",[11,1472,1473],{},"We are using the branch name, commit SHA, and timestamp to compose the image tag (we'll use this later as part of the continuous deployment tutorial).",[15,1475,1477,1480],{"type":1476},"error",[11,1478,1479],{},"Don't use your Docker credentials as plain text!",[48,1481,1482],{"v-slot:details":17},[11,1483,1484],{},"Keep the username and passwords as secrets or use another method to ensure they are not visible as plain text in your pipeline (I've used GitHub secrets).",[11,1486,1487,1488,1493,1494,1499],{},"We are building the image using the ",[177,1489,1490],{},[180,1491,1492],{},"docker build"," command against the Dockerfile mentioned above and tagging it with ",[177,1495,1496],{},[180,1497,1498],{},"-t"," using the tagging logic described earlier.",[69,1501,1503],{"className":1047,"code":1502,"language":1049,"meta":17,"style":17},"      - name: \"Build\"\n        run: |\n          docker build -f Dockerfile . -t ${{ env.IMAGE }}:${{ steps.prep.outputs.BUILD_ID }}\n",[75,1504,1505,1518,1525],{"__ignoreMap":17},[78,1506,1507,1509,1512,1514,1516],{"class":80,"line":81},[78,1508,255],{"class":1056},[78,1510,1511],{"class":215}," name:",[78,1513,608],{"class":258},[78,1515,754],{"class":215},[78,1517,614],{"class":258},[78,1519,1520,1523],{"class":80,"line":87},[78,1521,1522],{"class":1056},"        run:",[78,1524,437],{"class":436},[78,1526,1527,1530,1533,1537,1540,1543,1546,1550,1553,1557,1559,1562,1565,1568,1571,1573,1576,1578,1581,1583,1586,1588,1591,1593],{"class":80,"line":94},[78,1528,1529],{"class":1056},"          docker",[78,1531,1532],{"class":215}," build",[78,1534,1536],{"class":1535},"sqTyp"," -f",[78,1538,1539],{"class":215}," Dockerfile",[78,1541,1542],{"class":215}," .",[78,1544,1545],{"class":1535}," -t",[78,1547,1549],{"class":1548},"sDxrV"," ${",[78,1551,1552],{"class":294},"{ ",[78,1554,1556],{"class":1555},"sn_7u","env",[78,1558,901],{"class":294},[78,1560,1561],{"class":1555},"IMAGE",[78,1563,1564],{"class":1548}," }",[78,1566,1567],{"class":215},"}:",[78,1569,1570],{"class":1548},"${",[78,1572,1552],{"class":294},[78,1574,1575],{"class":1555},"steps",[78,1577,901],{"class":294},[78,1579,1580],{"class":1555},"prep",[78,1582,901],{"class":294},[78,1584,1585],{"class":1555},"outputs",[78,1587,901],{"class":294},[78,1589,1590],{"class":1555},"BUILD_ID",[78,1592,1564],{"class":1548},[78,1594,1595],{"class":215},"}\n",[883,1597,1599],{"id":1598},"push-to-registry","Push to Registry",[11,1601,1602],{},"Login is required for GitHub Packages access with write permissions since we are pushing an image to the registry.",[11,1604,1605,1606,1611,1612,901],{},"Then we use the images created in the previous step, tag them with the GitHub Container Registry prefix and/or the organization (ghcr.io/devozs) using the ",[177,1607,1608],{},[180,1609,1610],{},"docker tag"," command, and finally push them into the registry with ",[177,1613,1614],{},[180,1615,1616],{},"docker push",[69,1618,1620],{"className":1047,"code":1619,"language":1049,"meta":17,"style":17},"      - name: \"Build\"\n        run: |\n          docker build -f Dockerfile . -t ${{ env.IMAGE }}:${{ steps.prep.outputs.BUILD_ID }}\n\n      - name: Log in to the Container registry\n        uses: docker/login-action@v2\n        with:\n          registry: ghcr.io\n          username: ${{ github.actor }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: \"Docker Push\"\n        run: |\n          docker tag ${{ env.IMAGE }}:${{ steps.prep.outputs.BUILD_ID }} ${{ env.REGISTRY }}/${{ env.IMAGE }}:${{ steps.prep.outputs.BUILD_ID }}\n          docker push ${{ env.REGISTRY }}/${{ env.IMAGE }}:${{ steps.prep.outputs.BUILD_ID }}\n",[75,1621,1622,1634,1640,1690,1694,1718,1725,1730,1737,1758,1779,1783,1795,1801,1897],{"__ignoreMap":17},[78,1623,1624,1626,1628,1630,1632],{"class":80,"line":81},[78,1625,255],{"class":1056},[78,1627,1511],{"class":215},[78,1629,608],{"class":258},[78,1631,754],{"class":215},[78,1633,614],{"class":258},[78,1635,1636,1638],{"class":80,"line":87},[78,1637,1522],{"class":1056},[78,1639,437],{"class":436},[78,1641,1642,1644,1646,1648,1650,1652,1654,1656,1658,1660,1662,1664,1666,1668,1670,1672,1674,1676,1678,1680,1682,1684,1686,1688],{"class":80,"line":94},[78,1643,1529],{"class":1056},[78,1645,1532],{"class":215},[78,1647,1536],{"class":1535},[78,1649,1539],{"class":215},[78,1651,1542],{"class":215},[78,1653,1545],{"class":1535},[78,1655,1549],{"class":1548},[78,1657,1552],{"class":294},[78,1659,1556],{"class":1555},[78,1661,901],{"class":294},[78,1663,1561],{"class":1555},[78,1665,1564],{"class":1548},[78,1667,1567],{"class":215},[78,1669,1570],{"class":1548},[78,1671,1552],{"class":294},[78,1673,1575],{"class":1555},[78,1675,901],{"class":294},[78,1677,1580],{"class":1555},[78,1679,901],{"class":294},[78,1681,1585],{"class":1555},[78,1683,901],{"class":294},[78,1685,1590],{"class":1555},[78,1687,1564],{"class":1548},[78,1689,1595],{"class":215},[78,1691,1692],{"class":80,"line":100},[78,1693,91],{"emptyLinePlaceholder":90},[78,1695,1696,1698,1700,1703,1706,1709,1712,1715],{"class":80,"line":106},[78,1697,255],{"class":1056},[78,1699,1511],{"class":215},[78,1701,1702],{"class":215}," Log",[78,1704,1705],{"class":215}," in",[78,1707,1708],{"class":215}," to",[78,1710,1711],{"class":215}," the",[78,1713,1714],{"class":215}," Container",[78,1716,1717],{"class":215}," registry\n",[78,1719,1720,1723],{"class":80,"line":112},[78,1721,1722],{"class":1056},"        uses:",[78,1724,798],{"class":215},[78,1726,1727],{"class":80,"line":117},[78,1728,1729],{"class":1056},"        with:\n",[78,1731,1732,1735],{"class":80,"line":123},[78,1733,1734],{"class":1056},"          registry:",[78,1736,817],{"class":215},[78,1738,1739,1742,1744,1746,1749,1751,1754,1756],{"class":80,"line":129},[78,1740,1741],{"class":1056},"          username:",[78,1743,1549],{"class":1548},[78,1745,1552],{"class":294},[78,1747,1748],{"class":1555},"github",[78,1750,901],{"class":294},[78,1752,1753],{"class":1555},"actor",[78,1755,1564],{"class":1548},[78,1757,1595],{"class":215},[78,1759,1760,1763,1765,1767,1770,1772,1775,1777],{"class":80,"line":134},[78,1761,1762],{"class":1056},"          password:",[78,1764,1549],{"class":1548},[78,1766,1552],{"class":294},[78,1768,1769],{"class":1555},"secrets",[78,1771,901],{"class":294},[78,1773,1774],{"class":1555},"GITHUB_TOKEN",[78,1776,1564],{"class":1548},[78,1778,1595],{"class":215},[78,1780,1781],{"class":80,"line":140},[78,1782,91],{"emptyLinePlaceholder":90},[78,1784,1785,1787,1789,1791,1793],{"class":80,"line":146},[78,1786,255],{"class":1056},[78,1788,1511],{"class":215},[78,1790,608],{"class":258},[78,1792,858],{"class":215},[78,1794,614],{"class":258},[78,1796,1797,1799],{"class":80,"line":151},[78,1798,1522],{"class":1056},[78,1800,437],{"class":436},[78,1802,1803,1805,1808,1810,1812,1814,1816,1818,1820,1822,1824,1826,1828,1830,1832,1834,1836,1838,1840,1842,1845,1847,1849,1851,1853,1856,1858,1861,1863,1865,1867,1869,1871,1873,1875,1877,1879,1881,1883,1885,1887,1889,1891,1893,1895],{"class":80,"line":157},[78,1804,1529],{"class":1056},[78,1806,1807],{"class":215}," tag",[78,1809,1549],{"class":1548},[78,1811,1552],{"class":294},[78,1813,1556],{"class":1555},[78,1815,901],{"class":294},[78,1817,1561],{"class":1555},[78,1819,1564],{"class":1548},[78,1821,1567],{"class":215},[78,1823,1570],{"class":1548},[78,1825,1552],{"class":294},[78,1827,1575],{"class":1555},[78,1829,901],{"class":294},[78,1831,1580],{"class":1555},[78,1833,901],{"class":294},[78,1835,1585],{"class":1555},[78,1837,901],{"class":294},[78,1839,1590],{"class":1555},[78,1841,1564],{"class":1548},[78,1843,1844],{"class":215},"}",[78,1846,1549],{"class":1548},[78,1848,1552],{"class":294},[78,1850,1556],{"class":1555},[78,1852,901],{"class":294},[78,1854,1855],{"class":1555},"REGISTRY",[78,1857,1564],{"class":1548},[78,1859,1860],{"class":215},"}/",[78,1862,1570],{"class":1548},[78,1864,1552],{"class":294},[78,1866,1556],{"class":1555},[78,1868,901],{"class":294},[78,1870,1561],{"class":1555},[78,1872,1564],{"class":1548},[78,1874,1567],{"class":215},[78,1876,1570],{"class":1548},[78,1878,1552],{"class":294},[78,1880,1575],{"class":1555},[78,1882,901],{"class":294},[78,1884,1580],{"class":1555},[78,1886,901],{"class":294},[78,1888,1585],{"class":1555},[78,1890,901],{"class":294},[78,1892,1590],{"class":1555},[78,1894,1564],{"class":1548},[78,1896,1595],{"class":215},[78,1898,1899,1901,1904,1906,1908,1910,1912,1914,1916,1918,1920,1922,1924,1926,1928,1930,1932,1934,1936,1938,1940,1942,1944,1946,1948,1950,1952],{"class":80,"line":162},[78,1900,1529],{"class":1056},[78,1902,1903],{"class":215}," push",[78,1905,1549],{"class":1548},[78,1907,1552],{"class":294},[78,1909,1556],{"class":1555},[78,1911,901],{"class":294},[78,1913,1855],{"class":1555},[78,1915,1564],{"class":1548},[78,1917,1860],{"class":215},[78,1919,1570],{"class":1548},[78,1921,1552],{"class":294},[78,1923,1556],{"class":1555},[78,1925,901],{"class":294},[78,1927,1561],{"class":1555},[78,1929,1564],{"class":1548},[78,1931,1567],{"class":215},[78,1933,1570],{"class":1548},[78,1935,1552],{"class":294},[78,1937,1575],{"class":1555},[78,1939,901],{"class":294},[78,1941,1580],{"class":1555},[78,1943,901],{"class":294},[78,1945,1585],{"class":1555},[78,1947,901],{"class":294},[78,1949,1590],{"class":1555},[78,1951,1564],{"class":1548},[78,1953,1595],{"class":215},[28,1955,1957],{"id":1956},"validation","Validation",[11,1959,1960,1961],{},"Verify that the image exists in GitHub Packages (or any other container registry you are working with) with the relevant tag.\n",[913,1962],{"alt":1963,"src":1964,"title":1965},"Alt text","/images/blog/2023/09/08/blog-frontend-image.png","blog-frontend image in Github Registry",[11,1967,1968],{},"You can try and pull it, for example:",[69,1970,1972],{"className":1047,"code":1971,"language":1049,"meta":17,"style":17},"docker pull ghcr.io/\u003CYOUR_ORG>/blog-frontend:main-fc611440-1694297249\n",[75,1973,1974],{"__ignoreMap":17},[78,1975,1976,1978,1981,1984,1987,1990,1993,1995],{"class":80,"line":81},[78,1977,73],{"class":1056},[78,1979,1980],{"class":215}," pull",[78,1982,1983],{"class":215}," ghcr.io/",[78,1985,1986],{"class":436},"\u003C",[78,1988,1989],{"class":215},"YOUR_OR",[78,1991,1992],{"class":294},"G",[78,1994,1072],{"class":436},[78,1996,1997],{"class":215},"/blog-frontend:main-fc611440-1694297249\n",[1999,2000,2001],"style",{},"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 .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .skFRX, html code.shiki .skFRX{--shiki-default:#8FBCBB;--shiki-dark:#85E89D;--shiki-sepia:#F92672}html pre.shiki code .sUaCP, html code.shiki .sUaCP{--shiki-default:#ECEFF4;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .siq7d, html code.shiki .siq7d{--shiki-default:#A3BE8C;--shiki-dark:#9ECBFF;--shiki-sepia:#E6DB74}html pre.shiki code .sKfHY, html code.shiki .sKfHY{--shiki-default:#81A1C1;--shiki-dark:#79B8FF;--shiki-sepia:#AE81FF}html pre.shiki code .sQE_P, html code.shiki .sQE_P{--shiki-default:#ECEFF4;--shiki-dark:#9ECBFF;--shiki-sepia:#E6DB74}html pre.shiki code .sw3Zv, html code.shiki .sw3Zv{--shiki-default:#D8DEE9FF;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .s4BcI, html code.shiki .s4BcI{--shiki-default:#81A1C1;--shiki-dark:#F97583;--shiki-sepia:#F92672}html pre.shiki code .sr5Cr, html code.shiki .sr5Cr{--shiki-default:#616E88;--shiki-dark:#6A737D;--shiki-sepia:#88846F}html pre.shiki code .sNHwn, html code.shiki .sNHwn{--shiki-default:#88C0D0;--shiki-dark:#B392F0;--shiki-sepia:#A6E22E}html pre.shiki code .sX_qU, html code.shiki .sX_qU{--shiki-default:#B48EAD;--shiki-dark:#79B8FF;--shiki-sepia:#AE81FF}html pre.shiki code .sqTyp, html code.shiki .sqTyp{--shiki-default:#A3BE8C;--shiki-dark:#79B8FF;--shiki-sepia:#AE81FF}html pre.shiki code .sDxrV, html code.shiki .sDxrV{--shiki-default:#81A1C1;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .sn_7u, html code.shiki .sn_7u{--shiki-default:#D8DEE9;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}",{"title":17,"searchDepth":87,"depth":87,"links":2003},[2004,2005,2006,2012],{"id":30,"depth":87,"text":31},{"id":60,"depth":87,"text":61},{"id":168,"depth":87,"text":169,"children":2007},[2008,2009,2010,2011],{"id":885,"depth":94,"text":886},{"id":926,"depth":94,"text":927},{"id":1469,"depth":94,"text":1470},{"id":1598,"depth":94,"text":1599},{"id":1956,"depth":87,"text":1957},"./ci.png","2023-09-09T08:00:00.000Z",false,null,"md","CI, Test, Build, Docker, ContainerRegistry, GitHub",{"published":90},"/blog/2023/09/09/continuence-integration",{"title":5,"description":13},"continuence-integration","blog/2023/09/09/continuence-integration",[2025,468,754,2026,2027,2028],"CI","Docker","ContainerRegistry","GitHub","1HE64ECi3v6DCK-Wj3K4sX9wpsdTk3MIqfSYBBoP99Q",[2031,2759],{"id":2032,"title":2033,"author":6,"body":2034,"cover":2747,"date":2748,"description":17,"draft":2015,"excerpt":2016,"extension":2017,"github":2042,"keywords":2749,"meta":2750,"navigation":90,"path":2751,"seo":2752,"shortDesc":2016,"slug":2753,"stem":2754,"tags":2755,"video":2016,"__hash__":2758},"blog/blog/2024/08/06/gitlab-docker-python.md","GitLab CI with Python and Docker!",{"type":8,"value":2035,"toc":2725},[2036,2044,2047,2051,2083,2087,2093,2197,2201,2204,2211,2223,2226,2240,2243,2270,2273,2276,2280,2286,2343,2346,2350,2353,2373,2380,2383,2387,2390,2396,2400,2403,2423,2429,2433,2447,2451,2454,2468,2471,2488,2494,2498,2506,2528,2532,2541,2545,2548,2552,2555,2574,2578,2581,2651,2657,2661,2664,2668,2692,2696,2719,2722],[15,2037,2038],{"type":17},[11,2039,2040],{},[21,2041,26],{"href":2042,"rel":2043},"https://gitlab.com/devozs/counter-service",[25],[11,2045,2046],{},"This is a simple Flask application that counts the number of POST and GET requests it receives.",[28,2048,2050],{"id":2049},"setup","Setup",[2052,2053,2054,2065,2074],"ol",{},[906,2055,2056,2057],{},"Clone the repository:",[69,2058,2063],{"className":2059,"code":2061,"language":2062},[2060],"language-text","git clone https://gitlab.com/devozs/counter-service\ncd counter-service\n","text",[75,2064,2061],{"__ignoreMap":17},[906,2066,2067,2068],{},"Create a virtual environment and activate it:",[69,2069,2072],{"className":2070,"code":2071,"language":2062},[2060],"python -m venv venv\nsource venv/bin/activate  # On Windows, use `venv\\Scripts\\activate`\n",[75,2073,2071],{"__ignoreMap":17},[906,2075,2076,2077],{},"Install the requirements:",[69,2078,2081],{"className":2079,"code":2080,"language":2062},[2060],"pip install -r requirements.txt\n",[75,2082,2080],{"__ignoreMap":17},[28,2084,2086],{"id":2085},"project-structure","Project Structure",[69,2088,2091],{"className":2089,"code":2090,"language":2062},[2060],"counter_service/\n├── app/\n│   ├── __init__.py\n│   ├── routes.py\n│   └── counter.py\n├── certs/\n│   ├── generate-self-signed-cert.sh\n│   ├── cert.pem (generated)\n│   └── key.pem (generated)\n├── tests/\n│   ├── __init__.py\n│   └── test_routes.py\n├── config.py\n├── counter_service.py\n├── run_server.sh\n├── run_pytest.sh\n├── requirements.txt\n└── README.md\n",[75,2092,2090],{"__ignoreMap":17},[903,2094,2095,2121,2147,2161,2167,2173,2179,2185,2191],{},[906,2096,2097,2100,2101],{},[75,2098,2099],{},"app/",": Contains the main application code\n",[903,2102,2103,2109,2115],{},[906,2104,2105,2108],{},[75,2106,2107],{},"__init__.py",": Initializes the Flask application",[906,2110,2111,2114],{},[75,2112,2113],{},"routes.py",": Defines the application routes",[906,2116,2117,2120],{},[75,2118,2119],{},"counter.py",": Implements the Counter class for tracking requests",[906,2122,2123,2126,2127],{},[75,2124,2125],{},"certs/",": Contains SSL certificate-related files\n",[903,2128,2129,2135,2141],{},[906,2130,2131,2134],{},[75,2132,2133],{},"generate-self-signed-cert.sh",": Script to generate self-signed SSL certificates",[906,2136,2137,2140],{},[75,2138,2139],{},"cert.pem",": SSL certificate (generated by the script)",[906,2142,2143,2146],{},[75,2144,2145],{},"key.pem",": SSL private key (generated by the script)",[906,2148,2149,2152,2153],{},[75,2150,2151],{},"tests/",": Contains test files\n",[903,2154,2155],{},[906,2156,2157,2160],{},[75,2158,2159],{},"test_routes.py",": Tests for the application routes",[906,2162,2163,2166],{},[75,2164,2165],{},"config.py",": Configuration settings for the application",[906,2168,2169,2172],{},[75,2170,2171],{},"counter_service.py",": Main script to run the Flask application",[906,2174,2175,2178],{},[75,2176,2177],{},"run_server.sh",": Bash script to run the application with or without SSL",[906,2180,2181,2184],{},[75,2182,2183],{},"run_pytest.sh",": Bash script to run the application tests",[906,2186,2187,2190],{},[75,2188,2189],{},"requirements.txt",": List of Python dependencies",[906,2192,2193,2196],{},[75,2194,2195],{},"README.md",": Project documentation",[28,2198,2200],{"id":2199},"ssl-configuration","SSL Configuration",[11,2202,2203],{},"SSL configuration is separate from the environment configuration. You can run any environment (development, production, testing) with or without SSL by adding the 'ssl' argument to the script.",[11,2205,2206,2207,2210],{},"If running with SSL, the application expects the following files in the ",[75,2208,2209],{},"certs"," directory:",[903,2212,2213,2218],{},[906,2214,2215,2217],{},[75,2216,2139],{},": SSL certificate",[906,2219,2220,2222],{},[75,2221,2145],{},": SSL private key",[11,2224,2225],{},"To generate self-signed certificates for testing purposes, use the provided script:",[2052,2227,2228,2231],{},[906,2229,2230],{},"Navigate to the project root directory.",[906,2232,2233,2234],{},"Run the following command:\n",[69,2235,2238],{"className":2236,"code":2237,"language":2062},[2060],"(cd certs && ./generate-self-signed-cert.sh)\n",[75,2239,2237],{"__ignoreMap":17},[11,2241,2242],{},"This script will generate a self-signed certificate and key with the following details:",[903,2244,2245,2248,2251,2254,2257,2260,2263],{},[906,2246,2247],{},"Country: IL",[906,2249,2250],{},"State: Haifa",[906,2252,2253],{},"Locality: Haifa",[906,2255,2256],{},"Organization: DevOzs",[906,2258,2259],{},"Organizational Unit: DevOps",[906,2261,2262],{},"Common Name: localhost",[906,2264,2265,2266],{},"Email: ",[21,2267,2269],{"href":2268},"mailto:admin@devozs.com","admin@devozs.com",[11,2271,2272],{},"The certificate will be valid for 365 days.",[11,2274,2275],{},"Note: These self-signed certificates should only be used for development and testing purposes. For production environments, always use certificates from a trusted Certificate Authority.",[28,2277,2279],{"id":2278},"running-the-application","Running the Application",[11,2281,2282,2283,2285],{},"You can run the application using the ",[75,2284,2177],{}," script with various configurations:",[2052,2287,2288,2307,2316,2325,2334],{},[906,2289,2290,2291,2297,2300,2301],{},"Development mode without SSL (default):",[69,2292,2295],{"className":2293,"code":2294,"language":2062},[2060],"./run_server.sh\n",[75,2296,2294],{"__ignoreMap":17},[2298,2299],"br",{},"or",[69,2302,2305],{"className":2303,"code":2304,"language":2062},[2060],"./run_server.sh development\n",[75,2306,2304],{"__ignoreMap":17},[906,2308,2309,2310],{},"Production mode without SSL:",[69,2311,2314],{"className":2312,"code":2313,"language":2062},[2060],"./run_server.sh production\n",[75,2315,2313],{"__ignoreMap":17},[906,2317,2318,2319],{},"Testing mode without SSL:",[69,2320,2323],{"className":2321,"code":2322,"language":2062},[2060],"./run_server.sh testing\n",[75,2324,2322],{"__ignoreMap":17},[906,2326,2327,2328],{},"Development mode with SSL:",[69,2329,2332],{"className":2330,"code":2331,"language":2062},[2060],"./run_server.sh development ssl\n",[75,2333,2331],{"__ignoreMap":17},[906,2335,2336,2337],{},"Production mode with SSL:",[69,2338,2341],{"className":2339,"code":2340,"language":2062},[2060],"./run_server.sh production ssl\n",[75,2342,2340],{"__ignoreMap":17},[11,2344,2345],{},"Note: Running with SSL requires root privileges and will prompt for your password.",[28,2347,2349],{"id":2348},"using-the-api","Using the API",[11,2351,2352],{},"You can interact with the API using curl commands:",[2052,2354,2355,2364],{},[906,2356,2357,2358],{},"To make a GET request and view the current count:",[69,2359,2362],{"className":2360,"code":2361,"language":2062},[2060],"curl http://localhost:5000  # If running without SSL\ncurl -k https://localhost   # If running with SSL\n",[75,2363,2361],{"__ignoreMap":17},[906,2365,2366,2367],{},"To make a POST request and increment the counter:",[69,2368,2371],{"className":2369,"code":2370,"language":2062},[2060],"curl -X POST http://localhost:5000  # If running without SSL\ncurl -k -X POST https://localhost   # If running with SSL\n",[75,2372,2370],{"__ignoreMap":17},[11,2374,2375,2376,2379],{},"The ",[75,2377,2378],{},"-k"," flag is used to allow insecure connections when using self-signed certificates.",[11,2381,2382],{},"You can alternate between these commands to see the counter increment for both GET and POST requests.",[28,2384,2386],{"id":2385},"running-the-tests","Running the tests",[11,2388,2389],{},"Run the tests using pytest:",[69,2391,2394],{"className":2392,"code":2393,"language":2062},[2060],"pytest tests/\n",[75,2395,2393],{"__ignoreMap":17},[28,2397,2399],{"id":2398},"configuration","Configuration",[11,2401,2402],{},"The application uses different configuration classes for different environments:",[903,2404,2405,2411,2417],{},[906,2406,2407,2410],{},[75,2408,2409],{},"DevelopmentConfig",": Used for development.",[906,2412,2413,2416],{},[75,2414,2415],{},"TestingConfig",": Used for testing.",[906,2418,2419,2422],{},[75,2420,2421],{},"ProductionConfig",": Used for production.",[11,2424,2425,2426,2428],{},"You can modify these configurations in the ",[75,2427,2165],{}," file.",[28,2430,2432],{"id":2431},"api-endpoints","API Endpoints",[903,2434,2435,2441],{},[906,2436,2437,2440],{},[75,2438,2439],{},"GET /",": Returns the current count of POST and GET requests",[906,2442,2443,2446],{},[75,2444,2445],{},"POST /",": Increments the POST counter and returns the updated count",[28,2448,2450],{"id":2449},"running-tests","Running Tests",[11,2452,2453],{},"To run the tests for the Counter Service application:",[2052,2455,2456,2459],{},[906,2457,2458],{},"Ensure you're in the root directory of the project.",[906,2460,2461,2462],{},"Run the test script:",[69,2463,2466],{"className":2464,"code":2465,"language":2062},[2060],"./run_pytest.sh\n",[75,2467,2465],{"__ignoreMap":17},[11,2469,2470],{},"This script will:",[903,2472,2473,2476,2479,2485],{},[906,2474,2475],{},"Activate the virtual environment",[906,2477,2478],{},"Set the FLASK_ENV to 'testing'",[906,2480,2481,2482,2484],{},"Run pytest on the tests in the ",[75,2483,2151],{}," directory",[906,2486,2487],{},"Deactivate the virtual environment after tests are complete",[11,2489,2490,2491,2493],{},"The tests use the ",[75,2492,2415],{}," configuration, which is designed for testing purposes. This ensures that your tests are run in an environment that mimics your production setup but is safe for testing.",[883,2495,2497],{"id":2496},"test-configuration","Test Configuration",[11,2499,2375,2500,2502,2503,2505],{},[75,2501,2415],{}," in ",[75,2504,2165],{}," is used for running tests. You can modify this configuration to set up any specific settings needed for your test environment. For example:",[69,2507,2511],{"className":2508,"code":2509,"language":2510,"meta":17,"style":17},"language-python shiki shiki-themes nord github-dark monokai","class TestingConfig(Config):\n    TESTING = True\n    # Add any other test-specific configurations here\n","python",[75,2512,2513,2518,2523],{"__ignoreMap":17},[78,2514,2515],{"class":80,"line":81},[78,2516,2517],{},"class TestingConfig(Config):\n",[78,2519,2520],{"class":80,"line":87},[78,2521,2522],{},"    TESTING = True\n",[78,2524,2525],{"class":80,"line":94},[78,2526,2527],{},"    # Add any other test-specific configurations here\n",[883,2529,2531],{"id":2530},"writing-tests","Writing Tests",[11,2533,2534,2535,2537,2538,2540],{},"When writing new tests, ensure they are placed in the ",[75,2536,2151],{}," directory. The ",[75,2539,2159],{}," file contains examples of how to write tests for your routes.",[28,2542,2544],{"id":2543},"docker-setup","Docker Setup",[11,2546,2547],{},"This project includes a Dockerfile for containerizing the Counter Service application. The Docker setup is optimized for running the application on port 5000 without SSL.",[883,2549,2551],{"id":2550},"building-the-docker-image","Building the Docker Image",[11,2553,2554],{},"To build the Docker image:",[69,2556,2558],{"className":1047,"code":2557,"language":1049,"meta":17,"style":17},"docker build -t counter-service .\n",[75,2559,2560],{"__ignoreMap":17},[78,2561,2562,2564,2566,2568,2571],{"class":80,"line":81},[78,2563,73],{"class":1056},[78,2565,1532],{"class":215},[78,2567,1545],{"class":1535},[78,2569,2570],{"class":215}," counter-service",[78,2572,2573],{"class":215}," .\n",[883,2575,2577],{"id":2576},"running-the-application-in-docker","Running the Application in Docker",[11,2579,2580],{},"You can run the application in different modes using Docker:\nPlease note that the current Docker configuration does not support running the application with SSL on port 443. The application is set up to run on port 5000 without SSL when using Docker.",[2052,2582,2583,2628],{},[906,2584,2585,2586,2606,2300,2608],{},"Production mode (default):",[69,2587,2589],{"className":1047,"code":2588,"language":1049,"meta":17,"style":17},"docker run -p 5000:5000 counter-service\n",[75,2590,2591],{"__ignoreMap":17},[78,2592,2593,2595,2597,2600,2603],{"class":80,"line":81},[78,2594,73],{"class":1056},[78,2596,1060],{"class":215},[78,2598,2599],{"class":1535}," -p",[78,2601,2602],{"class":215}," 5000:5000",[78,2604,2605],{"class":215}," counter-service\n",[2298,2607],{},[69,2609,2611],{"className":1047,"code":2610,"language":1049,"meta":17,"style":17},"docker run -p 5000:5000 counter-service production\n",[75,2612,2613],{"__ignoreMap":17},[78,2614,2615,2617,2619,2621,2623,2625],{"class":80,"line":81},[78,2616,73],{"class":1056},[78,2618,1060],{"class":215},[78,2620,2599],{"class":1535},[78,2622,2602],{"class":215},[78,2624,2570],{"class":215},[78,2626,2627],{"class":215}," production\n",[906,2629,2630,2631],{},"Development mode:",[69,2632,2634],{"className":1047,"code":2633,"language":1049,"meta":17,"style":17},"docker run -p 5000:5000 counter-service development\n",[75,2635,2636],{"__ignoreMap":17},[78,2637,2638,2640,2642,2644,2646,2648],{"class":80,"line":81},[78,2639,73],{"class":1056},[78,2641,1060],{"class":215},[78,2643,2599],{"class":1535},[78,2645,2602],{"class":215},[78,2647,2570],{"class":215},[78,2649,2650],{"class":215}," development\n",[11,2652,2653,2654],{},"The application will be accessible at ",[75,2655,2656],{},"http://localhost:5000",[28,2658,2660],{"id":2659},"future-enhancements-and-notes","Future Enhancements and Notes",[11,2662,2663],{},"As we continue to develop and improve this project, we've identified several areas for future enhancements and some important notes:",[883,2665,2667],{"id":2666},"cicd-improvements","CI/CD Improvements",[903,2669,2670,2680,2686],{},[906,2671,2672,2675,2676,2679],{},[180,2673,2674],{},"Docker Run in CI",": The ",[75,2677,2678],{},"run_docker"," step at the end of the CI pipeline is currently included for demonstration purposes only. In a production environment, this step would be unnecessary as the image is already pushed to the registry.",[906,2681,2682,2685],{},[180,2683,2684],{},"Image Scanning",": Implement image scanning using tools like Trivy or similar to enhance security and identify vulnerabilities in our Docker images.",[906,2687,2688,2691],{},[180,2689,2690],{},"Merge Request Approvals",": Consider implementing additional merge request approvals and other GitLab approval processes to enhance code quality and security.",[883,2693,2695],{"id":2694},"dockerfile-optimizations","Dockerfile Optimizations",[903,2697,2698,2704,2710],{},[906,2699,2700,2703],{},[180,2701,2702],{},"Single Layer Build",": The current Dockerfile uses a two-layer build for demonstration purposes. In the future, we may optimize this to a single layer for efficiency.",[906,2705,2706,2709],{},[180,2707,2708],{},"Rootless Docker Build",": Explore using Podman for rootless Docker builds to enhance security.",[906,2711,2712,2715,2716,2718],{},[180,2713,2714],{},"Requirement Separation",": Investigate the option to split ",[75,2717,2189],{}," into separate files for application and testing dependencies.",[11,2720,2721],{},"These enhancements and notes serve as a roadmap for future development. They reflect our commitment to continuous improvement in terms of efficiency, security, and best practices. We welcome contributions and suggestions in any of these areas.",[1999,2723,2724],{},"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 .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sNHwn, html code.shiki .sNHwn{--shiki-default:#88C0D0;--shiki-dark:#B392F0;--shiki-sepia:#A6E22E}html pre.shiki code .siq7d, html code.shiki .siq7d{--shiki-default:#A3BE8C;--shiki-dark:#9ECBFF;--shiki-sepia:#E6DB74}html pre.shiki code .sqTyp, html code.shiki .sqTyp{--shiki-default:#A3BE8C;--shiki-dark:#79B8FF;--shiki-sepia:#AE81FF}",{"title":17,"searchDepth":87,"depth":87,"links":2726},[2727,2728,2729,2730,2731,2732,2733,2734,2735,2739,2743],{"id":2049,"depth":87,"text":2050},{"id":2085,"depth":87,"text":2086},{"id":2199,"depth":87,"text":2200},{"id":2278,"depth":87,"text":2279},{"id":2348,"depth":87,"text":2349},{"id":2385,"depth":87,"text":2386},{"id":2398,"depth":87,"text":2399},{"id":2431,"depth":87,"text":2432},{"id":2449,"depth":87,"text":2450,"children":2736},[2737,2738],{"id":2496,"depth":94,"text":2497},{"id":2530,"depth":94,"text":2531},{"id":2543,"depth":87,"text":2544,"children":2740},[2741,2742],{"id":2550,"depth":94,"text":2551},{"id":2576,"depth":94,"text":2577},{"id":2659,"depth":87,"text":2660,"children":2744},[2745,2746],{"id":2666,"depth":94,"text":2667},{"id":2694,"depth":94,"text":2695},"./workflow.png","2024-08-06T08:00:00.000Z","CI, Test, Build, Docker, Python, ECR",{},"/blog/2024/08/06/gitlab-docker-python",{"title":2033,"description":17},"gitlab-docker-python","blog/2024/08/06/gitlab-docker-python",[2025,468,754,2026,2756,2757],"Python","ECR","ODyYLIDNWHA6KBIYeP58hhDrVrKQ8jL36cQEVfAf1qk",{"id":2760,"title":2761,"author":6,"body":2762,"cover":2747,"date":4309,"description":2766,"draft":2015,"excerpt":2016,"extension":2017,"github":2773,"keywords":4310,"meta":4311,"navigation":90,"path":4312,"seo":4313,"shortDesc":2016,"slug":4314,"stem":4315,"tags":4316,"video":2016,"__hash__":4321},"blog/blog/2023/12/17/ci-cd-github-actions-flux-helm.md","GitHub Actions FluxCD & Helm",{"type":8,"value":2763,"toc":4293},[2764,2767,2775,2777,2780,2791,2794,2804,2808,2816,2825,2852,2855,2858,2967,2971,2999,3024,3030,3033,3048,3051,3054,3062,3066,3069,3093,3118,3122,3142,3145,3181,3183,3201,3205,3214,3344,3348,3351,3433,3437,3444,3564,3567,3570,3586,3590,3593,3601,3745,3749,3752,3757,3850,3920,4078,4083,4290],[11,2765,2766],{},"In the upcoming blog post, we will implement continuous integration and deployment with Helm and some GitOps characteristics",[15,2768,2769],{"type":17},[11,2770,2771],{},[21,2772,26],{"href":2773,"rel":2774},"https://github.com/devozs/maven-hello-world",[25],[28,2776,31],{"id":30},[11,2778,2779],{},"The primary focus of this blog post is to explore and showcase three key topics:",[2052,2781,2782,2785,2788],{},[906,2783,2784],{},"Utilizing GitHub Actions Workflow",[906,2786,2787],{},"Implementing deployment through Helm, with an emphasis on basic templating techniques",[906,2789,2790],{},"Employing FluxCD for deployment and its integration with the HelmRelease resource and Image Automation",[11,2792,2793],{},"Considering that the blog post serves as an illustrative example, it's important to note that there are additional aspects of Continuous Integration (CI) and Continuous Deployment (CD) that are not covered in detail here. For instance, in the realm of CI, aspects such as testing methodologies and strategies play a crucial role. Similarly, in the CD complex processes within the workflow are also beyond the scope of this example.",[15,2795,2796],{"type":43},[11,2797,2798,2799],{},"Make sure to install ",[21,2800,2803],{"href":2801,"rel":2802},"https://fluxcd.io/flux/installation/",[25],"FluxCD CLI and other prerequisites ",[28,2805,2807],{"id":2806},"repository-structure","Repository Structure",[11,2809,2810,2811,2815],{},"There are several ways to structure FluxCD repository ",[21,2812,57],{"href":2813,"rel":2814},"https://fluxcd.io/flux/guides/repository-structure/",[25],".\nFor the sake of simplicity in this tutorial, we will utilize a monorepo approach, consolidating all Kubernetes manifests within a single Git repository.",[11,2817,2818,2819,2824],{},"This is a simple Java Maven repository that was forked from ",[21,2820,2823],{"href":2821,"rel":2822},"https://github.com/ido83/maven-hello-world/",[25],"here"," and on top of it we`ve added",[2052,2826,2827,2830,2849],{},[906,2828,2829],{},"Dockerfile with simple multistage",[906,2831,2832,2833],{},"Two ways of deployment\n",[2052,2834,2835,2838],{},[906,2836,2837],{},"Kubectl and Helm CLIs",[906,2839,2840,2841],{},"FluxCD\n",[2052,2842,2843,2846],{},[906,2844,2845],{},"Using HelmRelease",[906,2847,2848],{},"Using Flux Image Controller / Policy",[906,2850,2851],{},"The above are orchestrated as CI and CD via GitHub Action Workflow",[11,2853,2854],{},"Our repository is structured in a manner of a monorepo, yet it has been simplified for the purposes of this example. This streamlined approach retains the core characteristics of a monorepo while ensuring ease of understanding and accessibility for demonstration purposes.",[11,2856,2857],{},"High level structure:",[69,2859,2861],{"className":1047,"code":2860,"language":1049,"meta":17,"style":17},"├── myapp (Java, Maven and Docker)\n└── deployment\n    ├── myapp-chart (Helm Chart)\n    └── flux\n        ├── apps\n        │   └── base\n        ├── infrastructure\n        │   └── dev\n        └── clusters\n            └── dev\n",[75,2862,2863,2885,2893,2909,2917,2925,2936,2943,2952,2960],{"__ignoreMap":17},[78,2864,2865,2868,2871,2874,2877,2880,2883],{"class":80,"line":81},[78,2866,2867],{"class":1056},"├──",[78,2869,2870],{"class":215}," myapp",[78,2872,2873],{"class":294}," (Java, ",[78,2875,2876],{"class":215},"Maven",[78,2878,2879],{"class":215}," and",[78,2881,2882],{"class":215}," Docker",[78,2884,1266],{"class":294},[78,2886,2887,2890],{"class":80,"line":87},[78,2888,2889],{"class":1056},"└──",[78,2891,2892],{"class":215}," deployment\n",[78,2894,2895,2898,2901,2904,2907],{"class":80,"line":94},[78,2896,2897],{"class":1056},"    ├──",[78,2899,2900],{"class":215}," myapp-chart",[78,2902,2903],{"class":294}," (Helm ",[78,2905,2906],{"class":215},"Chart",[78,2908,1266],{"class":294},[78,2910,2911,2914],{"class":80,"line":100},[78,2912,2913],{"class":1056},"    └──",[78,2915,2916],{"class":215}," flux\n",[78,2918,2919,2922],{"class":80,"line":106},[78,2920,2921],{"class":1056},"        ├──",[78,2923,2924],{"class":215}," apps\n",[78,2926,2927,2930,2933],{"class":80,"line":112},[78,2928,2929],{"class":1056},"        │",[78,2931,2932],{"class":215},"   └──",[78,2934,2935],{"class":215}," base\n",[78,2937,2938,2940],{"class":80,"line":117},[78,2939,2921],{"class":1056},[78,2941,2942],{"class":215}," infrastructure\n",[78,2944,2945,2947,2949],{"class":80,"line":123},[78,2946,2929],{"class":1056},[78,2948,2932],{"class":215},[78,2950,2951],{"class":215}," dev\n",[78,2953,2954,2957],{"class":80,"line":129},[78,2955,2956],{"class":1056},"        └──",[78,2958,2959],{"class":215}," clusters\n",[78,2961,2962,2965],{"class":80,"line":134},[78,2963,2964],{"class":1056},"            └──",[78,2966,2951],{"class":215},[883,2968,2970],{"id":2969},"github-workflow","GitHub Workflow",[11,2972,2973,2974,2979,2980,895,2985,2990,2991,2998],{},"We've breaken our pipline into three GitHub Action jobs): ",[177,2975,2976],{},[180,2977,2978],{},"app-build",", ",[177,2981,2982],{},[180,2983,2984],{},"docker-build",[177,2986,2987],{},[180,2988,2989],{},"deployment",".\nRefer to the workflow yaml located at ",[21,2992,2995],{"href":2993,"rel":2994},"https://github.com/devozs/maven-hello-world/blob/master/.github/workflows/myapp-workflow.yaml",[25],[75,2996,2997],{},".github/workflows/myapp-workflow.yaml"," for detailed information about the jobs and steps.\nNote: In case you want to run it on your repository make sure to create PAT in order to get repository access (same as describe below for setting GITHUB_TOKEN in order to locally run FluxCD)",[903,3000,3001,3007,3010],{},[906,3002,3003,3004],{},"app-build: Java build using Maven. i.e. ",[75,3005,3006],{},"mvn clean package",[906,3008,3009],{},"docker-build: In this phase, we build the container using the Dockerfile, just for the example the Dockerfile includes multilayer. This job stars running only after a succesful complition of app-build phase.",[906,3011,3012,3013],{},"deployment: triggering three independent jobs:\n",[903,3014,3015,3018,3021],{},[906,3016,3017],{},"Simple docker run",[906,3019,3020],{},"Kind cluster installation and Helm CLI (using the utility script)",[906,3022,3023],{},"Kind cluster installation and FluxCD bootstrap (using the utility script)",[11,3025,3026,3029],{},[913,3027],{"alt":915,"src":3028,"title":915},"/images/blog/2023/12/17/github-action-diagram.png","\nAt the buttom of the image you can see the logs of Helm CLI & FluxCD bootstrap steps (printing out the POD logs as explianed below).",[11,3031,3032],{},"Note: The repository also demostrate some branch protection:",[903,3034,3035,3038],{},[906,3036,3037],{},"Require a pull request & approvals before merging",[906,3039,3040,3041,3043,3044],{},"Require status checks to pass before merging, it will run the ",[75,3042,2978],{}," as part of the pull request\n",[913,3045],{"alt":3046,"src":3047,"title":922},"GitHub Branch Protection","/images/blog/2023/12/17/brach-protection.png",[28,3049,3050],{"id":2989},"Deployment",[11,3052,3053],{},"For your convinence you can use two utlity scripts:",[2052,3055,3056,3059],{},[906,3057,3058],{},"setup.sh - Setup the required softwares (the same script called during the CI/CD workflow)",[906,3060,3061],{},"deploy.sh - Automate the deployment proccess for both options: Kubectl & Help CLIs as well as using FluxCD",[883,3063,3065],{"id":3064},"using-kubectl-and-helm-cli","Using Kubectl and Helm CLI",[11,3067,3068],{},"Executing the script:",[69,3070,3072],{"className":1047,"code":3071,"language":1049,"meta":17,"style":17},"cd deployment\n./deploy.sh -v 1.0.0\n",[75,3073,3074,3082],{"__ignoreMap":17},[78,3075,3076,3080],{"class":80,"line":81},[78,3077,3079],{"class":3078},"sCWj5","cd",[78,3081,2892],{"class":215},[78,3083,3084,3087,3090],{"class":80,"line":87},[78,3085,3086],{"class":1056},"./deploy.sh",[78,3088,3089],{"class":1535}," -v",[78,3091,3092],{"class":1190}," 1.0.0\n",[11,3094,3095,3096,3101,3102,3105,3106,3109,3110,3113,3114,3117],{},"The script will install ",[21,3097,3100],{"href":3098,"rel":3099},"https://kind.sigs.k8s.io//",[25],"KIND Cluster"," and once the cluster is ready it will start the deployment using Helm Chart located at ",[75,3103,3104],{},"deployment/myapp-chart"," using ",[75,3107,3108],{},"values-dev.yaml"," templating as well as ",[75,3111,3112],{},"--set image.tag=${VERSION}"," as was set using the ",[75,3115,3116],{},"-v"," flag.",[883,3119,3121],{"id":3120},"using-fluxcd","Using FluxCD",[15,3123,3124,3132],{"type":43},[11,3125,3126,3127],{},"Make to create and set ",[21,3128,3131],{"href":3129,"rel":3130},"https://fluxcd.io/flux/installation/bootstrap/github/",[25],"GITHUB_TOKEN ",[48,3133,3134],{"v-slot:details":17},[11,3135,3136,3137],{},"Or if you are using different Git server follow the ",[21,3138,3141],{"href":3139,"rel":3140},"https://fluxcd.io/flux/installation/bootstrap/",[25],"bootstrap documentation",[11,3143,3144],{},"You`ll need to set the both GITHUB_TOKEN & GITHUB_USER, it will be used by the utitlity script for FluxCD bootstrap command:",[69,3146,3148],{"className":1047,"code":3147,"language":1049,"meta":17,"style":17},"export GITHUB_TOKEN=\u003CYOUR_PAT>\nexport GITHUB_USER=\u003CYOUR_GH_USER>\n",[75,3149,3150,3167],{"__ignoreMap":17},[78,3151,3152,3155,3158,3161,3164],{"class":80,"line":81},[78,3153,3154],{"class":436},"export",[78,3156,3157],{"class":1555}," GITHUB_TOKEN",[78,3159,3160],{"class":436},"=\u003C",[78,3162,3163],{"class":1555},"YOUR_PAT",[78,3165,3166],{"class":436},">\n",[78,3168,3169,3171,3174,3176,3179],{"class":80,"line":87},[78,3170,3154],{"class":436},[78,3172,3173],{"class":1555}," GITHUB_USER",[78,3175,3160],{"class":436},[78,3177,3178],{"class":1555},"YOUR_GH_USER",[78,3180,3166],{"class":436},[11,3182,3068],{},[69,3184,3186],{"className":1047,"code":3185,"language":1049,"meta":17,"style":17},"cd deployment\n./deploy.sh --flux\n",[75,3187,3188,3194],{"__ignoreMap":17},[78,3189,3190,3192],{"class":80,"line":81},[78,3191,3079],{"class":3078},[78,3193,2892],{"class":215},[78,3195,3196,3198],{"class":80,"line":87},[78,3197,3086],{"class":1056},[78,3199,3200],{"class":1535}," --flux\n",[883,3202,3204],{"id":3203},"verify-flux-pods","Verify Flux PODs",[11,3206,3207,3208,3213],{},"The following pods should be running on ",[177,3209,3210],{},[180,3211,3212],{},"flux-system"," namespace",[69,3215,3217],{"className":1047,"code":3216,"language":1049,"meta":17,"style":17},"kubectl get pod -n flux-system\n\nNAME                                           READY   STATUS    RESTARTS   AGE\nhelm-controller-69dbf9568-tp4x6                1/1     Running   0          2m5s\nimage-automation-controller-6bbc947558-h82k6   1/1     Running   0          2m5s\nimage-reflector-controller-7c49fdc68f-skrjs    1/1     Running   0          2m5s\nkustomize-controller-77bf676476-cj5rr          1/1     Running   0          2m5s\nnotification-controller-7bb6d7684d-k9s9l       1/1     Running   0          2m5s\nsource-controller-5996567c74-kt5nt             1/1     Running   0          2m5s\n",[75,3218,3219,3236,3240,3257,3274,3288,3302,3316,3330],{"__ignoreMap":17},[78,3220,3221,3224,3227,3230,3233],{"class":80,"line":81},[78,3222,3223],{"class":1056},"kubectl",[78,3225,3226],{"class":215}," get",[78,3228,3229],{"class":215}," pod",[78,3231,3232],{"class":1535}," -n",[78,3234,3235],{"class":215}," flux-system\n",[78,3237,3238],{"class":80,"line":87},[78,3239,91],{"emptyLinePlaceholder":90},[78,3241,3242,3245,3248,3251,3254],{"class":80,"line":94},[78,3243,3244],{"class":1056},"NAME",[78,3246,3247],{"class":215},"                                           READY",[78,3249,3250],{"class":215},"   STATUS",[78,3252,3253],{"class":215},"    RESTARTS",[78,3255,3256],{"class":215},"   AGE\n",[78,3258,3259,3262,3265,3268,3271],{"class":80,"line":100},[78,3260,3261],{"class":1056},"helm-controller-69dbf9568-tp4x6",[78,3263,3264],{"class":215},"                1/1",[78,3266,3267],{"class":215},"     Running",[78,3269,3270],{"class":1190},"   0",[78,3272,3273],{"class":215},"          2m5s\n",[78,3275,3276,3279,3282,3284,3286],{"class":80,"line":106},[78,3277,3278],{"class":1056},"image-automation-controller-6bbc947558-h82k6",[78,3280,3281],{"class":215},"   1/1",[78,3283,3267],{"class":215},[78,3285,3270],{"class":1190},[78,3287,3273],{"class":215},[78,3289,3290,3293,3296,3298,3300],{"class":80,"line":112},[78,3291,3292],{"class":1056},"image-reflector-controller-7c49fdc68f-skrjs",[78,3294,3295],{"class":215},"    1/1",[78,3297,3267],{"class":215},[78,3299,3270],{"class":1190},[78,3301,3273],{"class":215},[78,3303,3304,3307,3310,3312,3314],{"class":80,"line":117},[78,3305,3306],{"class":1056},"kustomize-controller-77bf676476-cj5rr",[78,3308,3309],{"class":215},"          1/1",[78,3311,3267],{"class":215},[78,3313,3270],{"class":1190},[78,3315,3273],{"class":215},[78,3317,3318,3321,3324,3326,3328],{"class":80,"line":123},[78,3319,3320],{"class":1056},"notification-controller-7bb6d7684d-k9s9l",[78,3322,3323],{"class":215},"       1/1",[78,3325,3267],{"class":215},[78,3327,3270],{"class":1190},[78,3329,3273],{"class":215},[78,3331,3332,3335,3338,3340,3342],{"class":80,"line":129},[78,3333,3334],{"class":1056},"source-controller-5996567c74-kt5nt",[78,3336,3337],{"class":215},"             1/1",[78,3339,3267],{"class":215},[78,3341,3270],{"class":1190},[78,3343,3273],{"class":215},[883,3345,3347],{"id":3346},"exploring-flux-kustomizations","Exploring Flux Kustomizations",[11,3349,3350],{},"Flux manage the deployment and its dependencies using a kustomization resource.\nTo watch the kustomizations list and thier status:",[69,3352,3354],{"className":1047,"code":3353,"language":1049,"meta":17,"style":17},"flux get kustomizations\n\nNAME                REVISION            SUSPENDED   READY   MESSAGE                              \nflux-system         master@sha1:d27dac72    False       True    Applied revision: master@sha1:d27dac72  \nmyapp-dev           master@sha1:d27dac72    False       True    Applied revision: master@sha1:d27dac72  \n",[75,3355,3356,3366,3370,3389,3414],{"__ignoreMap":17},[78,3357,3358,3361,3363],{"class":80,"line":81},[78,3359,3360],{"class":1056},"flux",[78,3362,3226],{"class":215},[78,3364,3365],{"class":215}," kustomizations\n",[78,3367,3368],{"class":80,"line":87},[78,3369,91],{"emptyLinePlaceholder":90},[78,3371,3372,3374,3377,3380,3383,3386],{"class":80,"line":94},[78,3373,3244],{"class":1056},[78,3375,3376],{"class":215},"                REVISION",[78,3378,3379],{"class":215},"            SUSPENDED",[78,3381,3382],{"class":215},"   READY",[78,3384,3385],{"class":215},"   MESSAGE",[78,3387,3388],{"class":294},"                              \n",[78,3390,3391,3393,3396,3399,3402,3405,3408,3411],{"class":80,"line":100},[78,3392,3212],{"class":1056},[78,3394,3395],{"class":215},"         master@sha1:d27dac72",[78,3397,3398],{"class":215},"    False",[78,3400,3401],{"class":215},"       True",[78,3403,3404],{"class":215},"    Applied",[78,3406,3407],{"class":215}," revision:",[78,3409,3410],{"class":215}," master@sha1:d27dac72",[78,3412,3413],{"class":294},"  \n",[78,3415,3416,3419,3422,3424,3426,3428,3430],{"class":80,"line":106},[78,3417,3418],{"class":1056},"myapp-dev",[78,3420,3421],{"class":215},"           master@sha1:d27dac72",[78,3423,3398],{"class":215},[78,3425,3401],{"class":215},[78,3427,3404],{"class":215},[78,3429,3407],{"class":215},[78,3431,3432],{"class":215}," master@sha1:d27dac72\n",[883,3434,3436],{"id":3435},"checking-the-logs","Checking the logs",[11,3438,3439,3440,3443],{},"Once the deployment is done, the script will get the POD i.e. ",[75,3441,3442],{},"myapp-6fc4d44c75-4jpvg"," logs and pring it out.",[69,3445,3447],{"className":1047,"code":3446,"language":1049,"meta":17,"style":17},"Starts new deployment...\nVersion: Managed By FluxCD\nDeploy using FluxCD\n\ndeployment \"myapp\" successfully rolled out\nPOD Image Details:\nImage: devozs/myapp:1.0.0\nImage ID: docker.io/devozs/myapp@sha256:0ac0962589fa30ca8d09f073a33fe7791359a295324aa237e2c5b6980f848415\nPOD Logs:\nHello World! DevOzs\nDeployment Done\n",[75,3448,3449,3460,3474,3484,3488,3509,3520,3528,3539,3546,3557],{"__ignoreMap":17},[78,3450,3451,3454,3457],{"class":80,"line":81},[78,3452,3453],{"class":1056},"Starts",[78,3455,3456],{"class":215}," new",[78,3458,3459],{"class":215}," deployment...\n",[78,3461,3462,3465,3468,3471],{"class":80,"line":87},[78,3463,3464],{"class":1056},"Version:",[78,3466,3467],{"class":215}," Managed",[78,3469,3470],{"class":215}," By",[78,3472,3473],{"class":215}," FluxCD\n",[78,3475,3476,3479,3482],{"class":80,"line":94},[78,3477,3478],{"class":1056},"Deploy",[78,3480,3481],{"class":215}," using",[78,3483,3473],{"class":215},[78,3485,3486],{"class":80,"line":100},[78,3487,91],{"emptyLinePlaceholder":90},[78,3489,3490,3492,3494,3497,3500,3503,3506],{"class":80,"line":106},[78,3491,2989],{"class":1056},[78,3493,608],{"class":258},[78,3495,3496],{"class":215},"myapp",[78,3498,3499],{"class":258},"\"",[78,3501,3502],{"class":215}," successfully",[78,3504,3505],{"class":215}," rolled",[78,3507,3508],{"class":215}," out\n",[78,3510,3511,3514,3517],{"class":80,"line":112},[78,3512,3513],{"class":1056},"POD",[78,3515,3516],{"class":215}," Image",[78,3518,3519],{"class":215}," Details:\n",[78,3521,3522,3525],{"class":80,"line":117},[78,3523,3524],{"class":1056},"Image:",[78,3526,3527],{"class":215}," devozs/myapp:1.0.0\n",[78,3529,3530,3533,3536],{"class":80,"line":123},[78,3531,3532],{"class":1056},"Image",[78,3534,3535],{"class":215}," ID:",[78,3537,3538],{"class":215}," docker.io/devozs/myapp@sha256:0ac0962589fa30ca8d09f073a33fe7791359a295324aa237e2c5b6980f848415\n",[78,3540,3541,3543],{"class":80,"line":129},[78,3542,3513],{"class":1056},[78,3544,3545],{"class":215}," Logs:\n",[78,3547,3548,3551,3554],{"class":80,"line":134},[78,3549,3550],{"class":1056},"Hello",[78,3552,3553],{"class":215}," World!",[78,3555,3556],{"class":215}," DevOzs\n",[78,3558,3559,3561],{"class":80,"line":140},[78,3560,3050],{"class":1056},[78,3562,3563],{"class":215}," Done\n",[883,3565,3566],{"id":1769},"Secrets",[11,3568,3569],{},"In this tutorial, we haven't utilized a secret manager like Azure Key Vault or any other valut. In case you need to use secrets (i.e when using Flux Image Repository with private regitry) make sure not to keep your secrets in Git.",[15,3571,3572,3575],{"type":1476},[11,3573,3574],{},"Make sure not to put your kubernetes secrets in Git",[48,3576,3577],{"v-slot:details":17},[11,3578,3579,3580,3585],{},"For testing purpuse you can add them to gitignore. Permenant solution would be to use key vault or other solution like sealed-secrets (",[21,3581,3584],{"href":3582,"rel":3583},"https://fluxcd.io/flux/guides/sealed-secrets/",[25],"works well with FluxCD",")",[883,3587,3589],{"id":3588},"docker-pull-secret","Docker Pull Secret",[11,3591,3592],{},"In case you are workign with images from private registry, Docker pull secret is will used in two ways:",[903,3594,3595,3598],{},[906,3596,3597],{},"Pull the deployment image from Github registry",[906,3599,3600],{},"FluxCD uses it to fetch new tags from Github registry",[69,3602,3604],{"className":198,"code":3603,"language":200,"meta":17,"style":17},"apiVersion: v1\nkind: Secret\nmetadata:\n  name: cr-secret\n  namespace: flux-system\ntype: kubernetes.io/dockerconfigjson\ndata:\n  .dockerconfigjson: \u003CPUT_YOURS>\n---\napiVersion: v1\nkind: Secret\nmetadata:\n  name: cr-secret\n  namespace: dev\ntype: kubernetes.io/dockerconfigjson\ndata:\n  .dockerconfigjson: \u003CPUT_YOURS>\n",[75,3605,3606,3616,3626,3633,3643,3652,3662,3669,3679,3685,3693,3701,3707,3715,3723,3731,3737],{"__ignoreMap":17},[78,3607,3608,3611,3613],{"class":80,"line":81},[78,3609,3610],{"class":207},"apiVersion",[78,3612,212],{"class":211},[78,3614,3615],{"class":215}," v1\n",[78,3617,3618,3621,3623],{"class":80,"line":87},[78,3619,3620],{"class":207},"kind",[78,3622,212],{"class":211},[78,3624,3625],{"class":215}," Secret\n",[78,3627,3628,3631],{"class":80,"line":94},[78,3629,3630],{"class":207},"metadata",[78,3632,229],{"class":211},[78,3634,3635,3638,3640],{"class":80,"line":100},[78,3636,3637],{"class":207},"  name",[78,3639,212],{"class":211},[78,3641,3642],{"class":215}," cr-secret\n",[78,3644,3645,3648,3650],{"class":80,"line":106},[78,3646,3647],{"class":207},"  namespace",[78,3649,212],{"class":211},[78,3651,3235],{"class":215},[78,3653,3654,3657,3659],{"class":80,"line":112},[78,3655,3656],{"class":207},"type",[78,3658,212],{"class":211},[78,3660,3661],{"class":215}," kubernetes.io/dockerconfigjson\n",[78,3663,3664,3667],{"class":80,"line":117},[78,3665,3666],{"class":207},"data",[78,3668,229],{"class":211},[78,3670,3671,3674,3676],{"class":80,"line":123},[78,3672,3673],{"class":207},"  .dockerconfigjson",[78,3675,212],{"class":211},[78,3677,3678],{"class":215}," \u003CPUT_YOURS>\n",[78,3680,3681],{"class":80,"line":129},[78,3682,3684],{"class":3683},"siby6","---\n",[78,3686,3687,3689,3691],{"class":80,"line":134},[78,3688,3610],{"class":207},[78,3690,212],{"class":211},[78,3692,3615],{"class":215},[78,3694,3695,3697,3699],{"class":80,"line":140},[78,3696,3620],{"class":207},[78,3698,212],{"class":211},[78,3700,3625],{"class":215},[78,3702,3703,3705],{"class":80,"line":146},[78,3704,3630],{"class":207},[78,3706,229],{"class":211},[78,3708,3709,3711,3713],{"class":80,"line":151},[78,3710,3637],{"class":207},[78,3712,212],{"class":211},[78,3714,3642],{"class":215},[78,3716,3717,3719,3721],{"class":80,"line":157},[78,3718,3647],{"class":207},[78,3720,212],{"class":211},[78,3722,2951],{"class":215},[78,3724,3725,3727,3729],{"class":80,"line":162},[78,3726,3656],{"class":207},[78,3728,212],{"class":211},[78,3730,3661],{"class":215},[78,3732,3733,3735],{"class":80,"line":329},[78,3734,3666],{"class":207},[78,3736,229],{"class":211},[78,3738,3739,3741,3743],{"class":80,"line":337},[78,3740,3673],{"class":207},[78,3742,212],{"class":211},[78,3744,3678],{"class":215},[883,3746,3748],{"id":3747},"the-fluxcd-magic","The FluxCD Magic 🪄",[11,3750,3751],{},"We are using two powerful Flux capabilities:",[903,3753,3754],{},[906,3755,3756],{},"Listen to new images in a certian repository and given pattern",[69,3758,3760],{"className":198,"code":3759,"language":200,"meta":17,"style":17},"apiVersion: image.toolkit.fluxcd.io/v1beta1\nkind: ImagePolicy\nmetadata:\n  name: myapp\n  namespace: flux-system\nspec:\n  imageRepositoryRef:\n    name: myapp\n  policy:\n    semver:\n      range: 1.0.x\n",[75,3761,3762,3771,3780,3786,3795,3803,3810,3817,3826,3833,3840],{"__ignoreMap":17},[78,3763,3764,3766,3768],{"class":80,"line":81},[78,3765,3610],{"class":207},[78,3767,212],{"class":211},[78,3769,3770],{"class":215}," image.toolkit.fluxcd.io/v1beta1\n",[78,3772,3773,3775,3777],{"class":80,"line":87},[78,3774,3620],{"class":207},[78,3776,212],{"class":211},[78,3778,3779],{"class":215}," ImagePolicy\n",[78,3781,3782,3784],{"class":80,"line":94},[78,3783,3630],{"class":207},[78,3785,229],{"class":211},[78,3787,3788,3790,3792],{"class":80,"line":100},[78,3789,3637],{"class":207},[78,3791,212],{"class":211},[78,3793,3794],{"class":215}," myapp\n",[78,3796,3797,3799,3801],{"class":80,"line":106},[78,3798,3647],{"class":207},[78,3800,212],{"class":211},[78,3802,3235],{"class":215},[78,3804,3805,3808],{"class":80,"line":112},[78,3806,3807],{"class":207},"spec",[78,3809,229],{"class":211},[78,3811,3812,3815],{"class":80,"line":117},[78,3813,3814],{"class":207},"  imageRepositoryRef",[78,3816,229],{"class":211},[78,3818,3819,3822,3824],{"class":80,"line":123},[78,3820,3821],{"class":207},"    name",[78,3823,212],{"class":211},[78,3825,3794],{"class":215},[78,3827,3828,3831],{"class":80,"line":129},[78,3829,3830],{"class":207},"  policy",[78,3832,229],{"class":211},[78,3834,3835,3838],{"class":80,"line":134},[78,3836,3837],{"class":207},"    semver",[78,3839,229],{"class":211},[78,3841,3842,3845,3847],{"class":80,"line":140},[78,3843,3844],{"class":207},"      range",[78,3846,212],{"class":211},[78,3848,3849],{"class":215}," 1.0.x\n",[69,3851,3853],{"className":198,"code":3852,"language":200,"meta":17,"style":17},"apiVersion: image.toolkit.fluxcd.io/v1beta1\nkind: ImageRepository\nmetadata:\n  name: myapp\n  namespace: flux-system\nspec:\n  image: devozs/myapp\n  interval: 1m0s\n\n",[75,3854,3855,3863,3872,3878,3886,3894,3900,3910],{"__ignoreMap":17},[78,3856,3857,3859,3861],{"class":80,"line":81},[78,3858,3610],{"class":207},[78,3860,212],{"class":211},[78,3862,3770],{"class":215},[78,3864,3865,3867,3869],{"class":80,"line":87},[78,3866,3620],{"class":207},[78,3868,212],{"class":211},[78,3870,3871],{"class":215}," ImageRepository\n",[78,3873,3874,3876],{"class":80,"line":94},[78,3875,3630],{"class":207},[78,3877,229],{"class":211},[78,3879,3880,3882,3884],{"class":80,"line":100},[78,3881,3637],{"class":207},[78,3883,212],{"class":211},[78,3885,3794],{"class":215},[78,3887,3888,3890,3892],{"class":80,"line":106},[78,3889,3647],{"class":207},[78,3891,212],{"class":211},[78,3893,3235],{"class":215},[78,3895,3896,3898],{"class":80,"line":112},[78,3897,3807],{"class":207},[78,3899,229],{"class":211},[78,3901,3902,3905,3907],{"class":80,"line":117},[78,3903,3904],{"class":207},"  image",[78,3906,212],{"class":211},[78,3908,3909],{"class":215}," devozs/myapp\n",[78,3911,3912,3915,3917],{"class":80,"line":123},[78,3913,3914],{"class":207},"  interval",[78,3916,212],{"class":211},[78,3918,3919],{"class":215}," 1m0s\n",[69,3921,3923],{"className":1047,"code":3922,"language":1049,"meta":17,"style":17},"flux get image policy -A\nNAMESPACE   NAME    LATEST IMAGE        READY   MESSAGE                                                           \ndev         myapp   devozs/myapp:1.0.40 True    Latest image tag for 'devozs/myapp' updated from 1.0.39 to 1.0.40   \n\nflux get image repository   \nNAMESPACE   NAME    LAST SCAN                   SUSPENDED   READY   MESSAGE                        \ndev         myapp   2023-12-18T00:43:48+02:00   False       True    successful scan: found 41 tags  \n\n",[75,3924,3925,3940,3962,4010,4014,4027,4049],{"__ignoreMap":17},[78,3926,3927,3929,3931,3934,3937],{"class":80,"line":81},[78,3928,3360],{"class":1056},[78,3930,3226],{"class":215},[78,3932,3933],{"class":215}," image",[78,3935,3936],{"class":215}," policy",[78,3938,3939],{"class":1535}," -A\n",[78,3941,3942,3945,3948,3951,3954,3957,3959],{"class":80,"line":87},[78,3943,3944],{"class":1056},"NAMESPACE",[78,3946,3947],{"class":215},"   NAME",[78,3949,3950],{"class":215},"    LATEST",[78,3952,3953],{"class":215}," IMAGE",[78,3955,3956],{"class":215},"        READY",[78,3958,3385],{"class":215},[78,3960,3961],{"class":294},"                                                           \n",[78,3963,3964,3967,3970,3973,3976,3979,3981,3983,3986,3988,3991,3994,3997,3999,4002,4004,4007],{"class":80,"line":94},[78,3965,3966],{"class":1056},"dev",[78,3968,3969],{"class":215},"         myapp",[78,3971,3972],{"class":215},"   devozs/myapp:1.0.40",[78,3974,3975],{"class":215}," True",[78,3977,3978],{"class":215},"    Latest",[78,3980,3933],{"class":215},[78,3982,1807],{"class":215},[78,3984,3985],{"class":215}," for",[78,3987,259],{"class":258},[78,3989,3990],{"class":215},"devozs/myapp",[78,3992,3993],{"class":258},"'",[78,3995,3996],{"class":215}," updated",[78,3998,1284],{"class":215},[78,4000,4001],{"class":1190}," 1.0.39",[78,4003,1708],{"class":215},[78,4005,4006],{"class":1190}," 1.0.40",[78,4008,4009],{"class":294},"   \n",[78,4011,4012],{"class":80,"line":100},[78,4013,91],{"emptyLinePlaceholder":90},[78,4015,4016,4018,4020,4022,4025],{"class":80,"line":106},[78,4017,3360],{"class":1056},[78,4019,3226],{"class":215},[78,4021,3933],{"class":215},[78,4023,4024],{"class":215}," repository",[78,4026,4009],{"class":294},[78,4028,4029,4031,4033,4036,4039,4042,4044,4046],{"class":80,"line":112},[78,4030,3944],{"class":1056},[78,4032,3947],{"class":215},[78,4034,4035],{"class":215},"    LAST",[78,4037,4038],{"class":215}," SCAN",[78,4040,4041],{"class":215},"                   SUSPENDED",[78,4043,3382],{"class":215},[78,4045,3385],{"class":215},[78,4047,4048],{"class":294},"                        \n",[78,4050,4051,4053,4055,4058,4061,4063,4066,4069,4072,4075],{"class":80,"line":117},[78,4052,3966],{"class":1056},[78,4054,3969],{"class":215},[78,4056,4057],{"class":215},"   2023-12-18T00:43:48+02:00",[78,4059,4060],{"class":215},"   False",[78,4062,3401],{"class":215},[78,4064,4065],{"class":215},"    successful",[78,4067,4068],{"class":215}," scan:",[78,4070,4071],{"class":215}," found",[78,4073,4074],{"class":1190}," 41",[78,4076,4077],{"class":215}," tags\n",[903,4079,4080],{},[906,4081,4082],{},"Update the resolved image tag in the relevant Kubernetes yaml (directly to the Deployment YAML or vie Kustomization YAML)",[69,4084,4086],{"className":198,"code":4085,"language":200,"meta":17,"style":17},"apiVersion: image.toolkit.fluxcd.io/v1beta1\nkind: ImageUpdateAutomation\nmetadata:\n  name: image-update-automation\n  namespace: flux-system\nspec:\n  interval: 1m10s\n  sourceRef:\n    kind: GitRepository\n    name: flux-system\n  git:\n    checkout:\n      ref:\n        branch: main\n    commit:\n      author:\n        email: fluxcdbot@users.noreply.github.com\n        name: fluxcdbot\n      messageTemplate: '{{range .Updated.Images}}{{println .}}{{end}}'\n    push:\n      branch: main\n  update:\n    path: ./gitops/clusters/dev\n    strategy: Setters\n",[75,4087,4088,4096,4105,4111,4120,4128,4134,4143,4150,4160,4168,4175,4182,4189,4199,4206,4213,4223,4233,4247,4254,4263,4270,4280],{"__ignoreMap":17},[78,4089,4090,4092,4094],{"class":80,"line":81},[78,4091,3610],{"class":207},[78,4093,212],{"class":211},[78,4095,3770],{"class":215},[78,4097,4098,4100,4102],{"class":80,"line":87},[78,4099,3620],{"class":207},[78,4101,212],{"class":211},[78,4103,4104],{"class":215}," ImageUpdateAutomation\n",[78,4106,4107,4109],{"class":80,"line":94},[78,4108,3630],{"class":207},[78,4110,229],{"class":211},[78,4112,4113,4115,4117],{"class":80,"line":100},[78,4114,3637],{"class":207},[78,4116,212],{"class":211},[78,4118,4119],{"class":215}," image-update-automation\n",[78,4121,4122,4124,4126],{"class":80,"line":106},[78,4123,3647],{"class":207},[78,4125,212],{"class":211},[78,4127,3235],{"class":215},[78,4129,4130,4132],{"class":80,"line":112},[78,4131,3807],{"class":207},[78,4133,229],{"class":211},[78,4135,4136,4138,4140],{"class":80,"line":117},[78,4137,3914],{"class":207},[78,4139,212],{"class":211},[78,4141,4142],{"class":215}," 1m10s\n",[78,4144,4145,4148],{"class":80,"line":123},[78,4146,4147],{"class":207},"  sourceRef",[78,4149,229],{"class":211},[78,4151,4152,4155,4157],{"class":80,"line":129},[78,4153,4154],{"class":207},"    kind",[78,4156,212],{"class":211},[78,4158,4159],{"class":215}," GitRepository\n",[78,4161,4162,4164,4166],{"class":80,"line":134},[78,4163,3821],{"class":207},[78,4165,212],{"class":211},[78,4167,3235],{"class":215},[78,4169,4170,4173],{"class":80,"line":140},[78,4171,4172],{"class":207},"  git",[78,4174,229],{"class":211},[78,4176,4177,4180],{"class":80,"line":146},[78,4178,4179],{"class":207},"    checkout",[78,4181,229],{"class":211},[78,4183,4184,4187],{"class":80,"line":151},[78,4185,4186],{"class":207},"      ref",[78,4188,229],{"class":211},[78,4190,4191,4194,4196],{"class":80,"line":157},[78,4192,4193],{"class":207},"        branch",[78,4195,212],{"class":211},[78,4197,4198],{"class":215}," main\n",[78,4200,4201,4204],{"class":80,"line":162},[78,4202,4203],{"class":207},"    commit",[78,4205,229],{"class":211},[78,4207,4208,4211],{"class":80,"line":329},[78,4209,4210],{"class":207},"      author",[78,4212,229],{"class":211},[78,4214,4215,4218,4220],{"class":80,"line":337},[78,4216,4217],{"class":207},"        email",[78,4219,212],{"class":211},[78,4221,4222],{"class":215}," fluxcdbot@users.noreply.github.com\n",[78,4224,4225,4228,4230],{"class":80,"line":351},[78,4226,4227],{"class":207},"        name",[78,4229,212],{"class":211},[78,4231,4232],{"class":215}," fluxcdbot\n",[78,4234,4235,4238,4240,4242,4245],{"class":80,"line":356},[78,4236,4237],{"class":207},"      messageTemplate",[78,4239,212],{"class":211},[78,4241,259],{"class":258},[78,4243,4244],{"class":215},"{{range .Updated.Images}}{{println .}}{{end}}",[78,4246,265],{"class":258},[78,4248,4249,4252],{"class":80,"line":373},[78,4250,4251],{"class":207},"    push",[78,4253,229],{"class":211},[78,4255,4256,4259,4261],{"class":80,"line":384},[78,4257,4258],{"class":207},"      branch",[78,4260,212],{"class":211},[78,4262,4198],{"class":215},[78,4264,4265,4268],{"class":80,"line":392},[78,4266,4267],{"class":207},"  update",[78,4269,229],{"class":211},[78,4271,4272,4275,4277],{"class":80,"line":407},[78,4273,4274],{"class":207},"    path",[78,4276,212],{"class":211},[78,4278,4279],{"class":215}," ./gitops/clusters/dev\n",[78,4281,4282,4285,4287],{"class":80,"line":412},[78,4283,4284],{"class":207},"    strategy",[78,4286,212],{"class":211},[78,4288,4289],{"class":215}," Setters\n",[1999,4291,4292],{},"html pre.shiki code .sNHwn, html code.shiki .sNHwn{--shiki-default:#88C0D0;--shiki-dark:#B392F0;--shiki-sepia:#A6E22E}html pre.shiki code .siq7d, html code.shiki .siq7d{--shiki-default:#A3BE8C;--shiki-dark:#9ECBFF;--shiki-sepia:#E6DB74}html pre.shiki code .sw3Zv, html code.shiki .sw3Zv{--shiki-default:#D8DEE9FF;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}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 .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sCWj5, html code.shiki .sCWj5{--shiki-default:#88C0D0;--shiki-dark:#79B8FF;--shiki-sepia:#66D9EF}html pre.shiki code .sqTyp, html code.shiki .sqTyp{--shiki-default:#A3BE8C;--shiki-dark:#79B8FF;--shiki-sepia:#AE81FF}html pre.shiki code .sX_qU, html code.shiki .sX_qU{--shiki-default:#B48EAD;--shiki-dark:#79B8FF;--shiki-sepia:#AE81FF}html pre.shiki code .s4BcI, html code.shiki .s4BcI{--shiki-default:#81A1C1;--shiki-dark:#F97583;--shiki-sepia:#F92672}html pre.shiki code .sn_7u, html code.shiki .sn_7u{--shiki-default:#D8DEE9;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .sQE_P, html code.shiki .sQE_P{--shiki-default:#ECEFF4;--shiki-dark:#9ECBFF;--shiki-sepia:#E6DB74}html pre.shiki code .skFRX, html code.shiki .skFRX{--shiki-default:#8FBCBB;--shiki-dark:#85E89D;--shiki-sepia:#F92672}html pre.shiki code .sUaCP, html code.shiki .sUaCP{--shiki-default:#ECEFF4;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .siby6, html code.shiki .siby6{--shiki-default:#D8DEE9FF;--shiki-dark:#B392F0;--shiki-sepia:#F8F8F2}",{"title":17,"searchDepth":87,"depth":87,"links":4294},[4295,4296,4299],{"id":30,"depth":87,"text":31},{"id":2806,"depth":87,"text":2807,"children":4297},[4298],{"id":2969,"depth":94,"text":2970},{"id":2989,"depth":87,"text":3050,"children":4300},[4301,4302,4303,4304,4305,4306,4307,4308],{"id":3064,"depth":94,"text":3065},{"id":3120,"depth":94,"text":3121},{"id":3203,"depth":94,"text":3204},{"id":3346,"depth":94,"text":3347},{"id":3435,"depth":94,"text":3436},{"id":1769,"depth":94,"text":3566},{"id":3588,"depth":94,"text":3589},{"id":3747,"depth":94,"text":3748},"2023-12-17T08:00:00.000Z","GitOps, CD, Deployment, FluxCD, GitHub, Helm",{"published":90},"/blog/2023/12/17/ci-cd-github-actions-flux-helm",{"title":2761,"description":2766},"ci-cd-github-actions-flux-helm","blog/2023/12/17/ci-cd-github-actions-flux-helm",[4317,4318,3050,4319,2028,4320],"GitOps","CD","FluxCD","Helm","SG8llZBvfzEOyOM0OvjiCxhkwt7qRbDxYBC6X4od99U"]