[{"data":1,"prerenderedAt":4747},["ShallowReactive",2],{"/blog/building-a-new-gitlab-docs-site-with-nanoc-gitlab-ci-and-gitlab-pages":3,"navigation-en-us":3986,"banner-en-us":4416,"footer-en-us":4426,"blog-post-authors-en-us-Connor Shea":4687,"blog-related-posts-en-us-building-a-new-gitlab-docs-site-with-nanoc-gitlab-ci-and-gitlab-pages":4702,"blog-share-wrapper-en-us":4727,"next-steps-en-us":4737},{"id":4,"title":5,"authors":6,"body":8,"category":3955,"date":3956,"description":3957,"extension":3958,"externalUrl":3959,"faq":3959,"featured":3960,"heroImage":3961,"meta":3962,"navigation":278,"path":3977,"seo":3978,"slug":3982,"stem":3983,"tags":3959,"template":3984,"updatedDate":3959,"__hash__":3985},"blogPosts/en-us/blog/building-a-new-gitlab-docs-site-with-nanoc-gitlab-ci-and-gitlab-pages.md","Building a new GitLab Docs site with Nanoc, GitLab CI, and GitLab Pages",[7],"Connor Shea",{"type":9,"value":10,"toc":3944},"minimark",[11,22,36,43,48,71,80,89,93,96,99,102,106,109,122,407,414,1249,1256,1747,1754,1788,1812,1830,1838,1842,1854,1869,1872,1876,1879,1902,1913,2745,2753,2760,2763,2773,2776,2779,2792,2798,2829,2835,2858,2868,2891,2908,2995,3003,3066,3074,3117,3120,3123,3130,3136,3141,3410,3438,3456,3464,3475,3480,3513,3538,3549,3561,3570,3572,3590,3600,3603,3637,3646,3658,3777,3783,3803,3809,3817,3828,3840,3843,3907,3914,3922,3926,3929,3937,3940],[12,13,14,15,21],"p",{},"We recently rebuilt ",[16,17,20],"a",{"href":18,"rel":19},"https://docs.gitlab.com",[],"docs.gitlab.com"," from scratch. Where previously the site was generated with a simple Ruby script, we now use a proper static site generator.",[12,23,24,25,29,30,35],{},"Check out the improvements we made, the structure we now use to deploy from specific directories in multiple repositories to a single website, build with ",[16,26,28],{"href":27},"/solutions/continuous-integration/","GitLab CI"," and deployed with ",[16,31,34],{"href":32,"rel":33},"https://pages.gitlab.io",[],"GitLab Pages",". Now our documentation has a nicer look and feel, is more pleasant to read through, and simpler and quicker to maintain.",[37,38,39],"ul",{},[40,41,42],"li",{},"TOC",[44,45,47],"h2",{"id":46},"improvements","Improvements",[12,49,50,51,56,57,61,62,66,67,70],{},"The old documentation website was pretty much just an HTML file, a stylesheet, and a ",[16,52,55],{"href":53,"rel":54},"https://gitlab.com/gitlab-com/doc-gitlab-com/blob/master/generate.rb",[],"Ruby script"," called ",[58,59,60],"code",{},"generate.rb",". While it worked, it was hard to update and not very flexible. It mostly laid dormant, only occasionally being touched by developers. The docs team really wanted to update the site to use a ",[16,63,65],{"href":64},"/blog/ssg-overview-gitlab-pages-part-3-examples-ci/","static site generator"," and take better advantage of ",[16,68,34],{"href":32,"rel":69},[],".",[12,72,73,74,79],{},"We chose ",[16,75,78],{"href":76,"rel":77},"https://nanoc.ws/",[],"Nanoc"," because it’s fast, it comes with a number of built-in helpers and filters (as well as the ability to create custom ones), and it’s built with Ruby. Overall, we think this was definitely the right choice. The author was very responsive and addressed anything we brought up. Kudos to him on the great project!",[12,81,82,83,88],{},"Other improvements include syntax highlighting with ",[16,84,87],{"href":85,"rel":86},"http://rouge.jneen.net/",[],"Rouge"," (no syntax highlighting was used at all on the old site), breadcrumbs for navigating between pages, and an improved overall design – especially on mobile.",[44,90,92],{"id":91},"requirements","Requirements",[12,94,95],{},"Our documentation site has some unique requirements that I haven’t seen mentioned or solved in any other companies’ blog posts. We have a few products with documentation we want to include in the site: Community Edition, Enterprise Edition, Omnibus GitLab, and GitLab Runner. In the future we’ll likely add more.",[12,97,98],{},"Each product has it own repository with its own documentation directory. This allows developers to add documentation in the same merge request they add a new feature or change some behavior, which prevents documentation from becoming outdated.",[12,100,101],{},"The site also needed to be flexible enough that we could add versioning to it in the future. Eventually, our goal is to replace the Help section in CE/EE with this Docs site, so we need to maintain older versions of the documentation on the Docs site for users on older versions of GitLab.",[44,103,105],{"id":104},"the-build-process","The build process",[12,107,108],{},"Given the requirements and separate repositories, we decided we’d just need to clone the repositories as part of the build process.",[12,110,111,112,115,116,121],{},"Inside Nanoc's config file (",[58,113,114],{},"nanoc.yml","), we ",[16,117,120],{"href":118,"rel":119},"https://gitlab.com/gitlab-org/gitlab-docs/blob/30f13e6a81bf9baeda95204b5524c6abf980b1e5/nanoc.yaml#L101-149",[],"have defined"," a hash of each of our products containing all the data we need. Here's an excerpt:",[123,124,129],"pre",{"className":125,"code":126,"language":127,"meta":128,"style":128},"language-yaml shiki shiki-themes github-light","products:\n  ce:\n    full_name: 'GitLab Community Edition'\n    short_name: 'Community Edition'\n    abbreviation: 'CE'\n    slug: 'ce'\n    index_file: 'README.*'\n    description: 'Browse user and administration documentation and guides for GitLab Community Edition.'\n    repo: 'https://gitlab.com/gitlab-org/gitlab-ce.git'\n    dirs:\n      temp_dir: 'tmp/ce/'\n      dest_dir: 'content/ce'\n      doc_dir:  'doc'\n\n...\n\n  runner:\n    full_name: 'GitLab Runner'\n    short_name: 'Runner'\n    abbreviation: 'RU'\n    slug: 'runner'\n    index_file: 'index.*'\n    description: 'Browse installation, configuration, maintenance, and troubleshooting documentation for GitLab Runner.'\n    repo: 'https://gitlab.com/gitlab-org/gitlab-runner.git'\n    dirs:\n      temp_dir: 'tmp/runner/'\n      dest_dir: 'content/runner'\n      doc_dir:  'docs'\n\n","yaml","",[58,130,131,144,152,165,176,187,198,209,220,231,239,250,261,273,280,287,292,300,310,320,330,340,350,360,370,377,387,397],{"__ignoreMap":128},[132,133,136,140],"span",{"class":134,"line":135},"line",1,[132,137,139],{"class":138},"shJU0","products",[132,141,143],{"class":142},"sgsFI",":\n",[132,145,147,150],{"class":134,"line":146},2,[132,148,149],{"class":138},"  ce",[132,151,143],{"class":142},[132,153,155,158,161],{"class":134,"line":154},3,[132,156,157],{"class":138},"    full_name",[132,159,160],{"class":142},": ",[132,162,164],{"class":163},"sYBdl","'GitLab Community Edition'\n",[132,166,168,171,173],{"class":134,"line":167},4,[132,169,170],{"class":138},"    short_name",[132,172,160],{"class":142},[132,174,175],{"class":163},"'Community Edition'\n",[132,177,179,182,184],{"class":134,"line":178},5,[132,180,181],{"class":138},"    abbreviation",[132,183,160],{"class":142},[132,185,186],{"class":163},"'CE'\n",[132,188,190,193,195],{"class":134,"line":189},6,[132,191,192],{"class":138},"    slug",[132,194,160],{"class":142},[132,196,197],{"class":163},"'ce'\n",[132,199,201,204,206],{"class":134,"line":200},7,[132,202,203],{"class":138},"    index_file",[132,205,160],{"class":142},[132,207,208],{"class":163},"'README.*'\n",[132,210,212,215,217],{"class":134,"line":211},8,[132,213,214],{"class":138},"    description",[132,216,160],{"class":142},[132,218,219],{"class":163},"'Browse user and administration documentation and guides for GitLab Community Edition.'\n",[132,221,223,226,228],{"class":134,"line":222},9,[132,224,225],{"class":138},"    repo",[132,227,160],{"class":142},[132,229,230],{"class":163},"'https://gitlab.com/gitlab-org/gitlab-ce.git'\n",[132,232,234,237],{"class":134,"line":233},10,[132,235,236],{"class":138},"    dirs",[132,238,143],{"class":142},[132,240,242,245,247],{"class":134,"line":241},11,[132,243,244],{"class":138},"      temp_dir",[132,246,160],{"class":142},[132,248,249],{"class":163},"'tmp/ce/'\n",[132,251,253,256,258],{"class":134,"line":252},12,[132,254,255],{"class":138},"      dest_dir",[132,257,160],{"class":142},[132,259,260],{"class":163},"'content/ce'\n",[132,262,264,267,270],{"class":134,"line":263},13,[132,265,266],{"class":138},"      doc_dir",[132,268,269],{"class":142},":  ",[132,271,272],{"class":163},"'doc'\n",[132,274,276],{"class":134,"line":275},14,[132,277,279],{"emptyLinePlaceholder":278},true,"\n",[132,281,283],{"class":134,"line":282},15,[132,284,286],{"class":285},"s7eDp","...\n",[132,288,290],{"class":134,"line":289},16,[132,291,279],{"emptyLinePlaceholder":278},[132,293,295,298],{"class":134,"line":294},17,[132,296,297],{"class":138},"  runner",[132,299,143],{"class":142},[132,301,303,305,307],{"class":134,"line":302},18,[132,304,157],{"class":138},[132,306,160],{"class":142},[132,308,309],{"class":163},"'GitLab Runner'\n",[132,311,313,315,317],{"class":134,"line":312},19,[132,314,170],{"class":138},[132,316,160],{"class":142},[132,318,319],{"class":163},"'Runner'\n",[132,321,323,325,327],{"class":134,"line":322},20,[132,324,181],{"class":138},[132,326,160],{"class":142},[132,328,329],{"class":163},"'RU'\n",[132,331,333,335,337],{"class":134,"line":332},21,[132,334,192],{"class":138},[132,336,160],{"class":142},[132,338,339],{"class":163},"'runner'\n",[132,341,343,345,347],{"class":134,"line":342},22,[132,344,203],{"class":138},[132,346,160],{"class":142},[132,348,349],{"class":163},"'index.*'\n",[132,351,353,355,357],{"class":134,"line":352},23,[132,354,214],{"class":138},[132,356,160],{"class":142},[132,358,359],{"class":163},"'Browse installation, configuration, maintenance, and troubleshooting documentation for GitLab Runner.'\n",[132,361,363,365,367],{"class":134,"line":362},24,[132,364,225],{"class":138},[132,366,160],{"class":142},[132,368,369],{"class":163},"'https://gitlab.com/gitlab-org/gitlab-runner.git'\n",[132,371,373,375],{"class":134,"line":372},25,[132,374,236],{"class":138},[132,376,143],{"class":142},[132,378,380,382,384],{"class":134,"line":379},26,[132,381,244],{"class":138},[132,383,160],{"class":142},[132,385,386],{"class":163},"'tmp/runner/'\n",[132,388,390,392,394],{"class":134,"line":389},27,[132,391,255],{"class":138},[132,393,160],{"class":142},[132,395,396],{"class":163},"'content/runner'\n",[132,398,400,402,404],{"class":134,"line":399},28,[132,401,266],{"class":138},[132,403,269],{"class":142},[132,405,406],{"class":163},"'docs'\n",[12,408,409,410,413],{},"We then have the ",[132,411,412],{},"Rakefile"," where the repos are cloned and the directories that\nNanoc needs are created:",[123,415,419],{"className":416,"code":417,"language":418,"meta":128,"style":128},"language-ruby shiki shiki-themes github-light","desc 'Pulls down the CE, EE, Omnibus and Runner git repos and merges the content of their doc directories into the nanoc site'\ntask :pull_repos do\n  require 'yaml'\n\n  # By default won't delete any directories, requires all relevant directories\n  # be empty. Run `RAKE_FORCE_DELETE=true rake pull_repos` to have directories\n  # deleted.\n  force_delete = ENV['RAKE_FORCE_DELETE']\n\n  # Parse the config file and create a hash.\n  config = YAML.load_file('./nanoc.yaml')\n\n  # Pull products data from the config.\n  ce = config[\"products\"][\"ce\"]\n  ee = config[\"products\"][\"ee\"]\n  omnibus = config[\"products\"][\"omnibus\"]\n  runner = config[\"products\"][\"runner\"]\n\n  products = [ce, ee, omnibus, runner]\n  dirs = []\n  products.each do |product|\n    dirs.push(product['dirs']['temp_dir'])\n    dirs.push(product['dirs']['dest_dir'])\n  end\n\n  if force_delete\n    puts \"WARNING: Are you sure you want to remove #{dirs.join(', ')}? [y/n]\"\n    exit unless STDIN.gets.index(/y/i) == 0\n\n    dirs.each do |dir|\n      puts \"\\n=> Deleting #{dir} if it exists\\n\"\n      FileUtils.rm_r(\"#{dir}\") if File.exist?(\"#{dir}\")\n    end\n  else\n    puts \"NOTE: The following directories must be empty otherwise this task \" +\n      \"will fail:\\n#{dirs.join(', ')}\"\n    puts \"If you want to force-delete the `tmp/` and `content/` folders so \\n\" +\n      \"the task will run without manual intervention, run \\n\" +\n      \"`RAKE_FORCE_DELETE=true rake pull_repos`.\"\n  end\n\n  dirs.each do |dir|\n    unless \"#{dir}\".start_with?(\"tmp\")\n\n      puts \"\\n=> Making an empty #{dir}\"\n      FileUtils.mkdir(\"#{dir}\") unless File.exist?(\"#{dir}\")\n    end\n  end\n\n  products.each do |product|\n    temp_dir = File.join(product['dirs']['temp_dir'])\n    puts \"\\n=> Cloning #{product['repo']} into #{temp_dir}\\n\"\n\n    `git clone #{product['repo']} #{temp_dir} --depth 1 --branch master`\n\n    temp_doc_dir = File.join(product['dirs']['temp_dir'], product['dirs']['doc_dir'], '.')\n    destination_dir = File.join(product['dirs']['dest_dir'])\n    puts \"\\n=> Copying #{temp_doc_dir} into #{destination_dir}\\n\"\n    FileUtils.cp_r(temp_doc_dir, destination_dir)\n  end\nend\n","ruby",[58,420,421,429,442,450,454,460,465,470,491,495,500,524,528,533,553,571,589,606,610,620,630,644,666,683,688,692,700,725,760,765,777,803,846,852,858,869,889,903,915,921,926,931,943,967,972,988,1027,1032,1037,1042,1053,1077,1108,1113,1131,1136,1176,1200,1224,1238,1243],{"__ignoreMap":128},[132,422,423,426],{"class":134,"line":135},[132,424,425],{"class":142},"desc ",[132,427,428],{"class":163},"'Pulls down the CE, EE, Omnibus and Runner git repos and merges the content of their doc directories into the nanoc site'\n",[132,430,431,434,438],{"class":134,"line":146},[132,432,433],{"class":142},"task ",[132,435,437],{"class":436},"sYu0t",":pull_repos",[132,439,441],{"class":440},"sD7c4"," do\n",[132,443,444,447],{"class":134,"line":154},[132,445,446],{"class":440},"  require",[132,448,449],{"class":163}," 'yaml'\n",[132,451,452],{"class":134,"line":167},[132,453,279],{"emptyLinePlaceholder":278},[132,455,456],{"class":134,"line":178},[132,457,459],{"class":458},"sAwPA","  # By default won't delete any directories, requires all relevant directories\n",[132,461,462],{"class":134,"line":189},[132,463,464],{"class":458},"  # be empty. Run `RAKE_FORCE_DELETE=true rake pull_repos` to have directories\n",[132,466,467],{"class":134,"line":200},[132,468,469],{"class":458},"  # deleted.\n",[132,471,472,476,479,482,485,488],{"class":134,"line":211},[132,473,475],{"class":474},"sqxcx","  force_delete",[132,477,478],{"class":440}," =",[132,480,481],{"class":436}," ENV",[132,483,484],{"class":142},"[",[132,486,487],{"class":163},"'RAKE_FORCE_DELETE'",[132,489,490],{"class":142},"]\n",[132,492,493],{"class":134,"line":222},[132,494,279],{"emptyLinePlaceholder":278},[132,496,497],{"class":134,"line":233},[132,498,499],{"class":458},"  # Parse the config file and create a hash.\n",[132,501,502,505,507,510,512,515,518,521],{"class":134,"line":241},[132,503,504],{"class":474},"  config",[132,506,478],{"class":440},[132,508,509],{"class":436}," YAML",[132,511,70],{"class":142},[132,513,514],{"class":285},"load_file",[132,516,517],{"class":142},"(",[132,519,520],{"class":163},"'./nanoc.yaml'",[132,522,523],{"class":142},")\n",[132,525,526],{"class":134,"line":252},[132,527,279],{"emptyLinePlaceholder":278},[132,529,530],{"class":134,"line":263},[132,531,532],{"class":458},"  # Pull products data from the config.\n",[132,534,535,537,539,542,545,548,551],{"class":134,"line":275},[132,536,149],{"class":474},[132,538,478],{"class":440},[132,540,541],{"class":142}," config[",[132,543,544],{"class":163},"\"products\"",[132,546,547],{"class":142},"][",[132,549,550],{"class":163},"\"ce\"",[132,552,490],{"class":142},[132,554,555,558,560,562,564,566,569],{"class":134,"line":282},[132,556,557],{"class":474},"  ee",[132,559,478],{"class":440},[132,561,541],{"class":142},[132,563,544],{"class":163},[132,565,547],{"class":142},[132,567,568],{"class":163},"\"ee\"",[132,570,490],{"class":142},[132,572,573,576,578,580,582,584,587],{"class":134,"line":289},[132,574,575],{"class":474},"  omnibus",[132,577,478],{"class":440},[132,579,541],{"class":142},[132,581,544],{"class":163},[132,583,547],{"class":142},[132,585,586],{"class":163},"\"omnibus\"",[132,588,490],{"class":142},[132,590,591,593,595,597,599,601,604],{"class":134,"line":294},[132,592,297],{"class":474},[132,594,478],{"class":440},[132,596,541],{"class":142},[132,598,544],{"class":163},[132,600,547],{"class":142},[132,602,603],{"class":163},"\"runner\"",[132,605,490],{"class":142},[132,607,608],{"class":134,"line":302},[132,609,279],{"emptyLinePlaceholder":278},[132,611,612,615,617],{"class":134,"line":312},[132,613,614],{"class":474},"  products",[132,616,478],{"class":440},[132,618,619],{"class":142}," [ce, ee, omnibus, runner]\n",[132,621,622,625,627],{"class":134,"line":322},[132,623,624],{"class":474},"  dirs",[132,626,478],{"class":440},[132,628,629],{"class":142}," []\n",[132,631,632,635,638,641],{"class":134,"line":332},[132,633,634],{"class":142},"  products.",[132,636,637],{"class":285},"each",[132,639,640],{"class":440}," do",[132,642,643],{"class":142}," |product|\n",[132,645,646,649,652,655,658,660,663],{"class":134,"line":342},[132,647,648],{"class":142},"    dirs.",[132,650,651],{"class":285},"push",[132,653,654],{"class":142},"(product[",[132,656,657],{"class":163},"'dirs'",[132,659,547],{"class":142},[132,661,662],{"class":163},"'temp_dir'",[132,664,665],{"class":142},"])\n",[132,667,668,670,672,674,676,678,681],{"class":134,"line":352},[132,669,648],{"class":142},[132,671,651],{"class":285},[132,673,654],{"class":142},[132,675,657],{"class":163},[132,677,547],{"class":142},[132,679,680],{"class":163},"'dest_dir'",[132,682,665],{"class":142},[132,684,685],{"class":134,"line":362},[132,686,687],{"class":440},"  end\n",[132,689,690],{"class":134,"line":372},[132,691,279],{"emptyLinePlaceholder":278},[132,693,694,697],{"class":134,"line":379},[132,695,696],{"class":440},"  if",[132,698,699],{"class":142}," force_delete\n",[132,701,702,705,708,711,714,716,719,722],{"class":134,"line":389},[132,703,704],{"class":436},"    puts",[132,706,707],{"class":163}," \"WARNING: Are you sure you want to remove ",[132,709,710],{"class":163},"#{dirs.",[132,712,713],{"class":285},"join",[132,715,517],{"class":163},[132,717,718],{"class":163},"', '",[132,720,721],{"class":163},")}",[132,723,724],{"class":163},"? [y/n]\"\n",[132,726,727,730,733,736,738,741,743,746,748,751,754,757],{"class":134,"line":399},[132,728,729],{"class":436},"    exit",[132,731,732],{"class":440}," unless",[132,734,735],{"class":436}," STDIN",[132,737,70],{"class":142},[132,739,740],{"class":436},"gets",[132,742,70],{"class":142},[132,744,745],{"class":285},"index",[132,747,517],{"class":142},[132,749,750],{"class":163},"/y/i",[132,752,753],{"class":142},") ",[132,755,756],{"class":440},"==",[132,758,759],{"class":436}," 0\n",[132,761,763],{"class":134,"line":762},29,[132,764,279],{"emptyLinePlaceholder":278},[132,766,768,770,772,774],{"class":134,"line":767},30,[132,769,648],{"class":142},[132,771,637],{"class":285},[132,773,640],{"class":440},[132,775,776],{"class":142}," |dir|\n",[132,778,780,783,786,789,792,795,798,800],{"class":134,"line":779},31,[132,781,782],{"class":436},"      puts",[132,784,785],{"class":163}," \"",[132,787,788],{"class":436},"\\n",[132,790,791],{"class":163},"=> Deleting ",[132,793,794],{"class":163},"#{dir}",[132,796,797],{"class":163}," if it exists",[132,799,788],{"class":436},[132,801,802],{"class":163},"\"\n",[132,804,806,809,811,814,816,819,821,823,825,828,831,833,836,838,840,842,844],{"class":134,"line":805},32,[132,807,808],{"class":436},"      FileUtils",[132,810,70],{"class":142},[132,812,813],{"class":285},"rm_r",[132,815,517],{"class":142},[132,817,818],{"class":163},"\"",[132,820,794],{"class":163},[132,822,818],{"class":163},[132,824,753],{"class":142},[132,826,827],{"class":440},"if",[132,829,830],{"class":436}," File",[132,832,70],{"class":142},[132,834,835],{"class":285},"exist?",[132,837,517],{"class":142},[132,839,818],{"class":163},[132,841,794],{"class":163},[132,843,818],{"class":163},[132,845,523],{"class":142},[132,847,849],{"class":134,"line":848},33,[132,850,851],{"class":440},"    end\n",[132,853,855],{"class":134,"line":854},34,[132,856,857],{"class":440},"  else\n",[132,859,861,863,866],{"class":134,"line":860},35,[132,862,704],{"class":436},[132,864,865],{"class":163}," \"NOTE: The following directories must be empty otherwise this task \"",[132,867,868],{"class":440}," +\n",[132,870,872,875,877,879,881,883,885,887],{"class":134,"line":871},36,[132,873,874],{"class":163},"      \"will fail:",[132,876,788],{"class":436},[132,878,710],{"class":163},[132,880,713],{"class":285},[132,882,517],{"class":163},[132,884,718],{"class":163},[132,886,721],{"class":163},[132,888,802],{"class":163},[132,890,892,894,897,899,901],{"class":134,"line":891},37,[132,893,704],{"class":436},[132,895,896],{"class":163}," \"If you want to force-delete the `tmp/` and `content/` folders so ",[132,898,788],{"class":436},[132,900,818],{"class":163},[132,902,868],{"class":440},[132,904,906,909,911,913],{"class":134,"line":905},38,[132,907,908],{"class":163},"      \"the task will run without manual intervention, run ",[132,910,788],{"class":436},[132,912,818],{"class":163},[132,914,868],{"class":440},[132,916,918],{"class":134,"line":917},39,[132,919,920],{"class":163},"      \"`RAKE_FORCE_DELETE=true rake pull_repos`.\"\n",[132,922,924],{"class":134,"line":923},40,[132,925,687],{"class":440},[132,927,929],{"class":134,"line":928},41,[132,930,279],{"emptyLinePlaceholder":278},[132,932,934,937,939,941],{"class":134,"line":933},42,[132,935,936],{"class":142},"  dirs.",[132,938,637],{"class":285},[132,940,640],{"class":440},[132,942,776],{"class":142},[132,944,946,949,951,953,955,957,960,962,965],{"class":134,"line":945},43,[132,947,948],{"class":440},"    unless",[132,950,785],{"class":163},[132,952,794],{"class":163},[132,954,818],{"class":163},[132,956,70],{"class":142},[132,958,959],{"class":285},"start_with?",[132,961,517],{"class":142},[132,963,964],{"class":163},"\"tmp\"",[132,966,523],{"class":142},[132,968,970],{"class":134,"line":969},44,[132,971,279],{"emptyLinePlaceholder":278},[132,973,975,977,979,981,984,986],{"class":134,"line":974},45,[132,976,782],{"class":436},[132,978,785],{"class":163},[132,980,788],{"class":436},[132,982,983],{"class":163},"=> Making an empty ",[132,985,794],{"class":163},[132,987,802],{"class":163},[132,989,991,993,995,998,1000,1002,1004,1006,1008,1011,1013,1015,1017,1019,1021,1023,1025],{"class":134,"line":990},46,[132,992,808],{"class":436},[132,994,70],{"class":142},[132,996,997],{"class":285},"mkdir",[132,999,517],{"class":142},[132,1001,818],{"class":163},[132,1003,794],{"class":163},[132,1005,818],{"class":163},[132,1007,753],{"class":142},[132,1009,1010],{"class":440},"unless",[132,1012,830],{"class":436},[132,1014,70],{"class":142},[132,1016,835],{"class":285},[132,1018,517],{"class":142},[132,1020,818],{"class":163},[132,1022,794],{"class":163},[132,1024,818],{"class":163},[132,1026,523],{"class":142},[132,1028,1030],{"class":134,"line":1029},47,[132,1031,851],{"class":440},[132,1033,1035],{"class":134,"line":1034},48,[132,1036,687],{"class":440},[132,1038,1040],{"class":134,"line":1039},49,[132,1041,279],{"emptyLinePlaceholder":278},[132,1043,1045,1047,1049,1051],{"class":134,"line":1044},50,[132,1046,634],{"class":142},[132,1048,637],{"class":285},[132,1050,640],{"class":440},[132,1052,643],{"class":142},[132,1054,1056,1059,1061,1063,1065,1067,1069,1071,1073,1075],{"class":134,"line":1055},51,[132,1057,1058],{"class":474},"    temp_dir",[132,1060,478],{"class":440},[132,1062,830],{"class":436},[132,1064,70],{"class":142},[132,1066,713],{"class":285},[132,1068,654],{"class":142},[132,1070,657],{"class":163},[132,1072,547],{"class":142},[132,1074,662],{"class":163},[132,1076,665],{"class":142},[132,1078,1080,1082,1084,1086,1089,1092,1095,1098,1101,1104,1106],{"class":134,"line":1079},52,[132,1081,704],{"class":436},[132,1083,785],{"class":163},[132,1085,788],{"class":436},[132,1087,1088],{"class":163},"=> Cloning ",[132,1090,1091],{"class":163},"#{product[",[132,1093,1094],{"class":163},"'repo'",[132,1096,1097],{"class":163},"]}",[132,1099,1100],{"class":163}," into ",[132,1102,1103],{"class":163},"#{temp_dir}",[132,1105,788],{"class":436},[132,1107,802],{"class":163},[132,1109,1111],{"class":134,"line":1110},53,[132,1112,279],{"emptyLinePlaceholder":278},[132,1114,1116,1119,1121,1123,1125,1128],{"class":134,"line":1115},54,[132,1117,1118],{"class":163},"    `git clone ",[132,1120,1091],{"class":163},[132,1122,1094],{"class":163},[132,1124,1097],{"class":163},[132,1126,1127],{"class":163}," #{temp_dir}",[132,1129,1130],{"class":163}," --depth 1 --branch master`\n",[132,1132,1134],{"class":134,"line":1133},55,[132,1135,279],{"emptyLinePlaceholder":278},[132,1137,1139,1142,1144,1146,1148,1150,1152,1154,1156,1158,1161,1163,1165,1168,1171,1174],{"class":134,"line":1138},56,[132,1140,1141],{"class":474},"    temp_doc_dir",[132,1143,478],{"class":440},[132,1145,830],{"class":436},[132,1147,70],{"class":142},[132,1149,713],{"class":285},[132,1151,654],{"class":142},[132,1153,657],{"class":163},[132,1155,547],{"class":142},[132,1157,662],{"class":163},[132,1159,1160],{"class":142},"], product[",[132,1162,657],{"class":163},[132,1164,547],{"class":142},[132,1166,1167],{"class":163},"'doc_dir'",[132,1169,1170],{"class":142},"], ",[132,1172,1173],{"class":163},"'.'",[132,1175,523],{"class":142},[132,1177,1179,1182,1184,1186,1188,1190,1192,1194,1196,1198],{"class":134,"line":1178},57,[132,1180,1181],{"class":474},"    destination_dir",[132,1183,478],{"class":440},[132,1185,830],{"class":436},[132,1187,70],{"class":142},[132,1189,713],{"class":285},[132,1191,654],{"class":142},[132,1193,657],{"class":163},[132,1195,547],{"class":142},[132,1197,680],{"class":163},[132,1199,665],{"class":142},[132,1201,1203,1205,1207,1209,1212,1215,1217,1220,1222],{"class":134,"line":1202},58,[132,1204,704],{"class":436},[132,1206,785],{"class":163},[132,1208,788],{"class":436},[132,1210,1211],{"class":163},"=> Copying ",[132,1213,1214],{"class":163},"#{temp_doc_dir}",[132,1216,1100],{"class":163},[132,1218,1219],{"class":163},"#{destination_dir}",[132,1221,788],{"class":436},[132,1223,802],{"class":163},[132,1225,1227,1230,1232,1235],{"class":134,"line":1226},59,[132,1228,1229],{"class":436},"    FileUtils",[132,1231,70],{"class":142},[132,1233,1234],{"class":285},"cp_r",[132,1236,1237],{"class":142},"(temp_doc_dir, destination_dir)\n",[132,1239,1241],{"class":134,"line":1240},60,[132,1242,687],{"class":440},[132,1244,1246],{"class":134,"line":1245},61,[132,1247,1248],{"class":440},"end\n",[12,1250,1251,1252,1255],{},"The ",[58,1253,1254],{},"pull_repos"," task inside the Rakefile is pretty self-explanatory if you know\nsome Ruby, but here's what it does:",[1257,1258,1259,1288,1364,1436,1538],"ol",{},[40,1260,1261,1263,1264],{},[58,1262,114],{}," is loaded since it contains the information we need for the\nvarious products:",[123,1265,1267],{"className":416,"code":1266,"language":418,"meta":128,"style":128},"config = YAML.load_file('./nanoc.yaml')\n",[58,1268,1269],{"__ignoreMap":128},[132,1270,1271,1274,1276,1278,1280,1282,1284,1286],{"class":134,"line":135},[132,1272,1273],{"class":474},"config",[132,1275,478],{"class":440},[132,1277,509],{"class":436},[132,1279,70],{"class":142},[132,1281,514],{"class":285},[132,1283,517],{"class":142},[132,1285,520],{"class":163},[132,1287,523],{"class":142},[40,1289,1290,1291],{},"The products data are pulled from the config:",[123,1292,1294],{"className":416,"code":1293,"language":418,"meta":128,"style":128},"ce = config[\"products\"][\"ce\"]\nee = config[\"products\"][\"ee\"]\nomnibus = config[\"products\"][\"omnibus\"]\nrunner = config[\"products\"][\"runner\"]\n",[58,1295,1296,1313,1330,1347],{"__ignoreMap":128},[132,1297,1298,1301,1303,1305,1307,1309,1311],{"class":134,"line":135},[132,1299,1300],{"class":474},"ce",[132,1302,478],{"class":440},[132,1304,541],{"class":142},[132,1306,544],{"class":163},[132,1308,547],{"class":142},[132,1310,550],{"class":163},[132,1312,490],{"class":142},[132,1314,1315,1318,1320,1322,1324,1326,1328],{"class":134,"line":146},[132,1316,1317],{"class":474},"ee",[132,1319,478],{"class":440},[132,1321,541],{"class":142},[132,1323,544],{"class":163},[132,1325,547],{"class":142},[132,1327,568],{"class":163},[132,1329,490],{"class":142},[132,1331,1332,1335,1337,1339,1341,1343,1345],{"class":134,"line":154},[132,1333,1334],{"class":474},"omnibus",[132,1336,478],{"class":440},[132,1338,541],{"class":142},[132,1340,544],{"class":163},[132,1342,547],{"class":142},[132,1344,586],{"class":163},[132,1346,490],{"class":142},[132,1348,1349,1352,1354,1356,1358,1360,1362],{"class":134,"line":167},[132,1350,1351],{"class":474},"runner",[132,1353,478],{"class":440},[132,1355,541],{"class":142},[132,1357,544],{"class":163},[132,1359,547],{"class":142},[132,1361,603],{"class":163},[132,1363,490],{"class":142},[40,1365,1366,1367],{},"The needed directories to be created (or deleted) are populated in an array:",[123,1368,1370],{"className":416,"code":1369,"language":418,"meta":128,"style":128},"products = [ce, ee, omnibus, runner]\ndirs = []\nproducts.each do |product|\n  dirs.push(product['dirs']['temp_dir'])\n  dirs.push(product['dirs']['dest_dir'])\nend\n",[58,1371,1372,1380,1389,1400,1416,1432],{"__ignoreMap":128},[132,1373,1374,1376,1378],{"class":134,"line":135},[132,1375,139],{"class":474},[132,1377,478],{"class":440},[132,1379,619],{"class":142},[132,1381,1382,1385,1387],{"class":134,"line":146},[132,1383,1384],{"class":474},"dirs",[132,1386,478],{"class":440},[132,1388,629],{"class":142},[132,1390,1391,1394,1396,1398],{"class":134,"line":154},[132,1392,1393],{"class":142},"products.",[132,1395,637],{"class":285},[132,1397,640],{"class":440},[132,1399,643],{"class":142},[132,1401,1402,1404,1406,1408,1410,1412,1414],{"class":134,"line":167},[132,1403,936],{"class":142},[132,1405,651],{"class":285},[132,1407,654],{"class":142},[132,1409,657],{"class":163},[132,1411,547],{"class":142},[132,1413,662],{"class":163},[132,1415,665],{"class":142},[132,1417,1418,1420,1422,1424,1426,1428,1430],{"class":134,"line":178},[132,1419,936],{"class":142},[132,1421,651],{"class":285},[132,1423,654],{"class":142},[132,1425,657],{"class":163},[132,1427,547],{"class":142},[132,1429,680],{"class":163},[132,1431,665],{"class":142},[132,1433,1434],{"class":134,"line":189},[132,1435,1248],{"class":440},[40,1437,1438,1439],{},"The empty directories are created:",[123,1440,1442],{"className":416,"code":1441,"language":418,"meta":128,"style":128},"dirs.each do |dir|\n  unless \"#{dir}\".start_with?(\"tmp\")\n\n    puts \"\\n=> Making an empty #{dir}\"\n    FileUtils.mkdir(\"#{dir}\") unless File.exist?(\"#{dir}\")\n  end\nend\n",[58,1443,1444,1455,1476,1480,1494,1530,1534],{"__ignoreMap":128},[132,1445,1446,1449,1451,1453],{"class":134,"line":135},[132,1447,1448],{"class":142},"dirs.",[132,1450,637],{"class":285},[132,1452,640],{"class":440},[132,1454,776],{"class":142},[132,1456,1457,1460,1462,1464,1466,1468,1470,1472,1474],{"class":134,"line":146},[132,1458,1459],{"class":440},"  unless",[132,1461,785],{"class":163},[132,1463,794],{"class":163},[132,1465,818],{"class":163},[132,1467,70],{"class":142},[132,1469,959],{"class":285},[132,1471,517],{"class":142},[132,1473,964],{"class":163},[132,1475,523],{"class":142},[132,1477,1478],{"class":134,"line":154},[132,1479,279],{"emptyLinePlaceholder":278},[132,1481,1482,1484,1486,1488,1490,1492],{"class":134,"line":167},[132,1483,704],{"class":436},[132,1485,785],{"class":163},[132,1487,788],{"class":436},[132,1489,983],{"class":163},[132,1491,794],{"class":163},[132,1493,802],{"class":163},[132,1495,1496,1498,1500,1502,1504,1506,1508,1510,1512,1514,1516,1518,1520,1522,1524,1526,1528],{"class":134,"line":178},[132,1497,1229],{"class":436},[132,1499,70],{"class":142},[132,1501,997],{"class":285},[132,1503,517],{"class":142},[132,1505,818],{"class":163},[132,1507,794],{"class":163},[132,1509,818],{"class":163},[132,1511,753],{"class":142},[132,1513,1010],{"class":440},[132,1515,830],{"class":436},[132,1517,70],{"class":142},[132,1519,835],{"class":285},[132,1521,517],{"class":142},[132,1523,818],{"class":163},[132,1525,794],{"class":163},[132,1527,818],{"class":163},[132,1529,523],{"class":142},[132,1531,1532],{"class":134,"line":189},[132,1533,687],{"class":440},[132,1535,1536],{"class":134,"line":200},[132,1537,1248],{"class":440},[40,1539,1540,1541,1544,1545,1548,1549,1552,1553,1732,1735,1737,1738,1740,1741,1743,1744,70],{},"We finally copy the contents of the documentation directory (defined by\n",[58,1542,1543],{},"doc_dir",") for each product from ",[58,1546,1547],{},"tmp/"," to ",[58,1550,1551],{},"content/",":",[123,1554,1556],{"className":416,"code":1555,"language":418,"meta":128,"style":128},"products.each do |product|\n  temp_dir = File.join(product['dirs']['temp_dir'])\n  puts \"\\n=> Cloning #{product['repo']} into #{temp_dir}\\n\"\n\n  `git clone #{product['repo']} #{temp_dir} --depth 1 --branch master`\n\n  temp_doc_dir = File.join(product['dirs']['temp_dir'], product['dirs']['doc_dir'], '.')\n  destination_dir = File.join(product['dirs']['dest_dir'])\n  puts \"\\n=> Copying #{temp_doc_dir} into #{destination_dir}\\n\"\n  FileUtils.cp_r(temp_doc_dir, destination_dir)\nend\n",[58,1557,1558,1568,1591,1616,1620,1635,1639,1674,1697,1717,1728],{"__ignoreMap":128},[132,1559,1560,1562,1564,1566],{"class":134,"line":135},[132,1561,1393],{"class":142},[132,1563,637],{"class":285},[132,1565,640],{"class":440},[132,1567,643],{"class":142},[132,1569,1570,1573,1575,1577,1579,1581,1583,1585,1587,1589],{"class":134,"line":146},[132,1571,1572],{"class":474},"  temp_dir",[132,1574,478],{"class":440},[132,1576,830],{"class":436},[132,1578,70],{"class":142},[132,1580,713],{"class":285},[132,1582,654],{"class":142},[132,1584,657],{"class":163},[132,1586,547],{"class":142},[132,1588,662],{"class":163},[132,1590,665],{"class":142},[132,1592,1593,1596,1598,1600,1602,1604,1606,1608,1610,1612,1614],{"class":134,"line":154},[132,1594,1595],{"class":436},"  puts",[132,1597,785],{"class":163},[132,1599,788],{"class":436},[132,1601,1088],{"class":163},[132,1603,1091],{"class":163},[132,1605,1094],{"class":163},[132,1607,1097],{"class":163},[132,1609,1100],{"class":163},[132,1611,1103],{"class":163},[132,1613,788],{"class":436},[132,1615,802],{"class":163},[132,1617,1618],{"class":134,"line":167},[132,1619,279],{"emptyLinePlaceholder":278},[132,1621,1622,1625,1627,1629,1631,1633],{"class":134,"line":178},[132,1623,1624],{"class":163},"  `git clone ",[132,1626,1091],{"class":163},[132,1628,1094],{"class":163},[132,1630,1097],{"class":163},[132,1632,1127],{"class":163},[132,1634,1130],{"class":163},[132,1636,1637],{"class":134,"line":189},[132,1638,279],{"emptyLinePlaceholder":278},[132,1640,1641,1644,1646,1648,1650,1652,1654,1656,1658,1660,1662,1664,1666,1668,1670,1672],{"class":134,"line":200},[132,1642,1643],{"class":474},"  temp_doc_dir",[132,1645,478],{"class":440},[132,1647,830],{"class":436},[132,1649,70],{"class":142},[132,1651,713],{"class":285},[132,1653,654],{"class":142},[132,1655,657],{"class":163},[132,1657,547],{"class":142},[132,1659,662],{"class":163},[132,1661,1160],{"class":142},[132,1663,657],{"class":163},[132,1665,547],{"class":142},[132,1667,1167],{"class":163},[132,1669,1170],{"class":142},[132,1671,1173],{"class":163},[132,1673,523],{"class":142},[132,1675,1676,1679,1681,1683,1685,1687,1689,1691,1693,1695],{"class":134,"line":211},[132,1677,1678],{"class":474},"  destination_dir",[132,1680,478],{"class":440},[132,1682,830],{"class":436},[132,1684,70],{"class":142},[132,1686,713],{"class":285},[132,1688,654],{"class":142},[132,1690,657],{"class":163},[132,1692,547],{"class":142},[132,1694,680],{"class":163},[132,1696,665],{"class":142},[132,1698,1699,1701,1703,1705,1707,1709,1711,1713,1715],{"class":134,"line":222},[132,1700,1595],{"class":436},[132,1702,785],{"class":163},[132,1704,788],{"class":436},[132,1706,1211],{"class":163},[132,1708,1214],{"class":163},[132,1710,1100],{"class":163},[132,1712,1219],{"class":163},[132,1714,788],{"class":436},[132,1716,802],{"class":163},[132,1718,1719,1722,1724,1726],{"class":134,"line":233},[132,1720,1721],{"class":436},"  FileUtils",[132,1723,70],{"class":142},[132,1725,1234],{"class":285},[132,1727,1237],{"class":142},[132,1729,1730],{"class":134,"line":241},[132,1731,1248],{"class":440},[1733,1734],"br",{},[58,1736,1551],{}," is where Nanoc looks for the actual site’s Markdown files. To prevent the ",[58,1739,1547],{}," and ",[58,1742,1551],{}," subdirectories from being pushed after testing the site locally, they’re excluded by ",[58,1745,1746],{},".gitignore",[12,1748,1749,1750,1753],{},"In the future we may speed this up further by caching the ",[58,1751,1752],{},"tmp"," folder in CI. The task would need to be updated to check if the local repository is up-to-date with the remote, only cloning if they differ.",[12,1755,1756,1757,1760,1761,1766,1767,1775,1776,1781,1782,1787],{},"Now that all the needed files are in order, we run ",[58,1758,1759],{},"nanoc"," to build the static sire. Nanoc runs each Markdown file through a series of ",[16,1762,1765],{"href":1763,"rel":1764},"https://nanoc.ws/doc/reference/filters/",[],"filters"," defined by rules in the ",[16,1768,1771,1774],{"href":1769,"rel":1770},"https://gitlab.com/gitlab-org/gitlab-docs/blob/30f13e6a81bf9baeda95204b5524c6abf980b1e5/Rules",[],[58,1772,1773],{},"Rules"," file",". We currently use ",[16,1777,1780],{"href":1778,"rel":1779},"https://github.com/vmg/redcarpet",[],"Redcarpet"," as the Markdown parser along with Rouge for syntax highlighting, as well as some custom filters. We plan on ",[16,1783,1786],{"href":1784,"rel":1785},"https://gitlab.com/gitlab-org/gitlab-docs/issues/50",[],"moving to Kramdown as our Markdown parser in the future"," as it provides some nice stuff like user-defined Table of Contents, etc.",[12,1789,1790,1791,1799,1800,70],{},"We also define some filters inside the ",[16,1792,1795,1798],{"href":1793,"rel":1794},"https://gitlab.com/gitlab-org/gitlab-docs/tree/30f13e6a81bf9baeda95204b5524c6abf980b1e5/lib/filters",[],[58,1796,1797],{},"lib/filters/"," directory",",\nincluding one that ",[16,1801,1804,1805,1808,1809],{"href":1802,"rel":1803},"https://gitlab.com/gitlab-org/gitlab-docs/blob/30f13e6a81bf9baeda95204b5524c6abf980b1e5/lib/filters/markdown_to_html_ext.rb",[],"replaces any ",[58,1806,1807],{},".md"," extension with ",[58,1810,1811],{},".html",[12,1813,1814,1815,1818,1819,1822,1823,70],{},"The Table of Contents (ToC) is generated for each page except when it's named ",[58,1816,1817],{},"index.md","\nor ",[58,1820,1821],{},"README.md"," as we usually use these as landing pages to index other\ndocumentation files and we don't want them to have a ToC. All this and some\nother options that Redcarpet provides ",[16,1824,1827,1828,1774],{"href":1825,"rel":1826},"https://gitlab.com/gitlab-org/gitlab-docs/blob/30f13e6a81bf9baeda95204b5524c6abf980b1e5/Rules#L33-51",[],"are defined in the ",[58,1829,1773],{},[12,1831,1832,1833,70],{},"For more on the specifics of building a site with Nanoc, see ",[16,1834,1837],{"href":1835,"rel":1836},"https://nanoc.ws/doc/tutorial/",[],"the Nanoc tutorial",[44,1839,1841],{"id":1840},"taking-advantage-of-gitlab-to-put-everything-together","Taking advantage of GitLab to put everything together",[12,1843,1844,1845,1849,1850,1853],{},"The new docs portal is hosted on GitLab.com at ",[16,1846,1847],{"href":1847,"rel":1848},"https://gitlab.com/gitlab-org/gitlab-docs",[],".\nIn that project we create issues, discuss things, open merge requests in feature\nbranches, iterate on feedback and finally merge things in the ",[58,1851,1852],{},"master"," branch.\nAgain, the documentation source files are not stored in this repository, if\nyou want to contribute, you'd have to open a merge request to the respective\nproject.",[12,1855,1856,1857,1859,1860,1865,1866,70],{},"There are 3 key things we use to test, build, deploy and host the Nanoc site\nall built into GitLab: ",[16,1858,28],{"href":27},", ",[16,1861,1864],{"href":1862,"rel":1863},"https://docs.gitlab.com/ci/review_apps/",[],"Review Apps","\nand ",[16,1867,34],{"href":32,"rel":1868},[],[12,1870,1871],{},"Let's break it down to pieces.",[1873,1874,28],"h3",{"id":1875},"gitlab-ci",[12,1877,1878],{},"GitLab CI is responsible of all the stages that we go through to publish\nnew documentation: test, build and deploy.",[12,1880,1881,1882,1887,1888,1895,1896,1901],{},"Nanoc has a built-in system of ",[16,1883,1886],{"href":1884,"rel":1885},"https://nanoc.ws/doc/testing/",[],"Checks",", including HTML/CSS and internal/external link validation. With GitLab CI we test with the internal link checker (set to ",[16,1889,1892],{"href":1890,"rel":1891},"https://docs.gitlab.com/ci/yaml/#allow_failure",[],[58,1893,1894],{},"allow failure",") and also verify that the site compiles without errors. We also run a ",[16,1897,1900],{"href":1898,"rel":1899},"https://github.com/sasstools/sass-lint",[],"SCSS Linter"," to make sure our SCSS looks uniform.",[12,1903,1904,1905,1912],{},"Our full ",[16,1906,1909],{"href":1907,"rel":1908},"https://gitlab.com/gitlab-org/gitlab-docs/blob/master/.gitlab-ci.yml",[],[58,1910,1911],{},".gitlab-ci.yml"," file looks like this. We'll break it down to make it clear what it is doing:",[123,1914,1916],{"className":125,"code":1915,"language":127,"meta":128,"style":128},"image: ruby:2.3\n\n## Cache the vendor/ruby directory\ncache:\n  key: \"ruby-231\"\n  paths:\n  - vendor/ruby\n\n## Define the stages\nstages:\n  - test\n  - deploy\n\n## Before each job's script is run, run the commands below\nbefore_script:\n  - ruby -v\n  - bundle install --jobs 4 --path vendor\n\n## Make sure the site builds successfully\nverify_compile:\n  stage: test\n  script:\n    - rake pull_repos\n    - nanoc\n  artifacts:\n    paths:\n      - public\n    expire_in: 1w\n  except:\n    - master\n  tags:\n    - docker\n\n## Check for dead internal links using Nanoc's built-in tool\ninternal_links:\n  stage: test\n  script:\n    - rake pull_repos\n    - nanoc\n    - nanoc check internal_links\n  allow_failure: true\n  tags:\n    - docker\n\n## Make sure our SCSS stylesheets are correctly defined\nscss_lint:\n  stage: test\n  script:\n    - npx sass-lint '**/*.scss' -v\n  tags:\n    - docker\n\n## A job that deploys a review app to a dedicated server running Nginx.\nreview:\n  stage: deploy\n  variables:\n    GIT_STRATEGY: none\n  before_script: []\n  cache: {}\n  script:\n    - rsync -av --delete public /srv/nginx/pages/$CI_BUILD_REF_NAME\n  environment:\n    name: review/$CI_BUILD_REF_NAME\n    url: http://$CI_BUILD_REF_NAME.$APPS_DOMAIN\n    on_stop: review_stop\n  only:\n    - branches@gitlab-org/gitlab-docs\n  except:\n    - master\n  tags:\n    - nginx\n    - review-apps\n\n## Stop the review app\nreview_stop:\n  stage: deploy\n  variables:\n    GIT_STRATEGY: none\n  before_script: []\n  artifacts: {}\n  cache: {}\n  dependencies: []\n  script:\n    - rm -rf public /srv/nginx/pages/$CI_BUILD_REF_NAME\n  when: manual\n  environment:\n    name: review/$CI_BUILD_REF_NAME\n    action: stop\n  only:\n    - branches@gitlab-org/gitlab-docs\n  except:\n    - master\n  tags:\n    - nginx\n    - review-apps\n\n## Deploy the static site to GitLab Pages\npages:\n  stage: deploy\n  environment:\n    name: production\n    url: https://docs.gitlab.com\n  script:\n    - rake pull_repos\n    - nanoc\n    # Symlink all README.html to index.html\n    - for i in `find public -name README.html`; do ln -sf README.html $(dirname $i)/index.html; done\n  artifacts:\n    paths:\n    - public\n    expire_in: 1h\n  only:\n    - master@gitlab-org/gitlab-docs\n  tags:\n    - docker\n\n",[58,1917,1918,1928,1932,1937,1944,1954,1961,1969,1973,1978,1985,1992,1999,2003,2008,2015,2022,2029,2033,2038,2045,2054,2061,2069,2076,2083,2090,2098,2108,2115,2122,2129,2136,2140,2145,2152,2160,2166,2172,2178,2185,2195,2201,2207,2211,2216,2223,2231,2237,2244,2250,2256,2260,2265,2272,2280,2287,2297,2305,2313,2319,2326,2334,2345,2356,2367,2375,2383,2390,2397,2404,2412,2420,2425,2431,2439,2448,2455,2464,2471,2478,2485,2493,2500,2508,2519,2526,2535,2546,2553,2560,2567,2574,2581,2588,2595,2600,2606,2614,2623,2630,2640,2650,2657,2664,2671,2677,2685,2692,2699,2706,2716,2723,2731,2738],{"__ignoreMap":128},[132,1919,1920,1923,1925],{"class":134,"line":135},[132,1921,1922],{"class":138},"image",[132,1924,160],{"class":142},[132,1926,1927],{"class":163},"ruby:2.3\n",[132,1929,1930],{"class":134,"line":146},[132,1931,279],{"emptyLinePlaceholder":278},[132,1933,1934],{"class":134,"line":154},[132,1935,1936],{"class":458},"## Cache the vendor/ruby directory\n",[132,1938,1939,1942],{"class":134,"line":167},[132,1940,1941],{"class":138},"cache",[132,1943,143],{"class":142},[132,1945,1946,1949,1951],{"class":134,"line":178},[132,1947,1948],{"class":138},"  key",[132,1950,160],{"class":142},[132,1952,1953],{"class":163},"\"ruby-231\"\n",[132,1955,1956,1959],{"class":134,"line":189},[132,1957,1958],{"class":138},"  paths",[132,1960,143],{"class":142},[132,1962,1963,1966],{"class":134,"line":200},[132,1964,1965],{"class":142},"  - ",[132,1967,1968],{"class":163},"vendor/ruby\n",[132,1970,1971],{"class":134,"line":211},[132,1972,279],{"emptyLinePlaceholder":278},[132,1974,1975],{"class":134,"line":222},[132,1976,1977],{"class":458},"## Define the stages\n",[132,1979,1980,1983],{"class":134,"line":233},[132,1981,1982],{"class":138},"stages",[132,1984,143],{"class":142},[132,1986,1987,1989],{"class":134,"line":241},[132,1988,1965],{"class":142},[132,1990,1991],{"class":163},"test\n",[132,1993,1994,1996],{"class":134,"line":252},[132,1995,1965],{"class":142},[132,1997,1998],{"class":163},"deploy\n",[132,2000,2001],{"class":134,"line":263},[132,2002,279],{"emptyLinePlaceholder":278},[132,2004,2005],{"class":134,"line":275},[132,2006,2007],{"class":458},"## Before each job's script is run, run the commands below\n",[132,2009,2010,2013],{"class":134,"line":282},[132,2011,2012],{"class":138},"before_script",[132,2014,143],{"class":142},[132,2016,2017,2019],{"class":134,"line":289},[132,2018,1965],{"class":142},[132,2020,2021],{"class":163},"ruby -v\n",[132,2023,2024,2026],{"class":134,"line":294},[132,2025,1965],{"class":142},[132,2027,2028],{"class":163},"bundle install --jobs 4 --path vendor\n",[132,2030,2031],{"class":134,"line":302},[132,2032,279],{"emptyLinePlaceholder":278},[132,2034,2035],{"class":134,"line":312},[132,2036,2037],{"class":458},"## Make sure the site builds successfully\n",[132,2039,2040,2043],{"class":134,"line":322},[132,2041,2042],{"class":138},"verify_compile",[132,2044,143],{"class":142},[132,2046,2047,2050,2052],{"class":134,"line":332},[132,2048,2049],{"class":138},"  stage",[132,2051,160],{"class":142},[132,2053,1991],{"class":163},[132,2055,2056,2059],{"class":134,"line":342},[132,2057,2058],{"class":138},"  script",[132,2060,143],{"class":142},[132,2062,2063,2066],{"class":134,"line":352},[132,2064,2065],{"class":142},"    - ",[132,2067,2068],{"class":163},"rake pull_repos\n",[132,2070,2071,2073],{"class":134,"line":362},[132,2072,2065],{"class":142},[132,2074,2075],{"class":163},"nanoc\n",[132,2077,2078,2081],{"class":134,"line":372},[132,2079,2080],{"class":138},"  artifacts",[132,2082,143],{"class":142},[132,2084,2085,2088],{"class":134,"line":379},[132,2086,2087],{"class":138},"    paths",[132,2089,143],{"class":142},[132,2091,2092,2095],{"class":134,"line":389},[132,2093,2094],{"class":142},"      - ",[132,2096,2097],{"class":163},"public\n",[132,2099,2100,2103,2105],{"class":134,"line":399},[132,2101,2102],{"class":138},"    expire_in",[132,2104,160],{"class":142},[132,2106,2107],{"class":163},"1w\n",[132,2109,2110,2113],{"class":134,"line":762},[132,2111,2112],{"class":138},"  except",[132,2114,143],{"class":142},[132,2116,2117,2119],{"class":134,"line":767},[132,2118,2065],{"class":142},[132,2120,2121],{"class":163},"master\n",[132,2123,2124,2127],{"class":134,"line":779},[132,2125,2126],{"class":138},"  tags",[132,2128,143],{"class":142},[132,2130,2131,2133],{"class":134,"line":805},[132,2132,2065],{"class":142},[132,2134,2135],{"class":163},"docker\n",[132,2137,2138],{"class":134,"line":848},[132,2139,279],{"emptyLinePlaceholder":278},[132,2141,2142],{"class":134,"line":854},[132,2143,2144],{"class":458},"## Check for dead internal links using Nanoc's built-in tool\n",[132,2146,2147,2150],{"class":134,"line":860},[132,2148,2149],{"class":138},"internal_links",[132,2151,143],{"class":142},[132,2153,2154,2156,2158],{"class":134,"line":871},[132,2155,2049],{"class":138},[132,2157,160],{"class":142},[132,2159,1991],{"class":163},[132,2161,2162,2164],{"class":134,"line":891},[132,2163,2058],{"class":138},[132,2165,143],{"class":142},[132,2167,2168,2170],{"class":134,"line":905},[132,2169,2065],{"class":142},[132,2171,2068],{"class":163},[132,2173,2174,2176],{"class":134,"line":917},[132,2175,2065],{"class":142},[132,2177,2075],{"class":163},[132,2179,2180,2182],{"class":134,"line":923},[132,2181,2065],{"class":142},[132,2183,2184],{"class":163},"nanoc check internal_links\n",[132,2186,2187,2190,2192],{"class":134,"line":928},[132,2188,2189],{"class":138},"  allow_failure",[132,2191,160],{"class":142},[132,2193,2194],{"class":436},"true\n",[132,2196,2197,2199],{"class":134,"line":933},[132,2198,2126],{"class":138},[132,2200,143],{"class":142},[132,2202,2203,2205],{"class":134,"line":945},[132,2204,2065],{"class":142},[132,2206,2135],{"class":163},[132,2208,2209],{"class":134,"line":969},[132,2210,279],{"emptyLinePlaceholder":278},[132,2212,2213],{"class":134,"line":974},[132,2214,2215],{"class":458},"## Make sure our SCSS stylesheets are correctly defined\n",[132,2217,2218,2221],{"class":134,"line":990},[132,2219,2220],{"class":138},"scss_lint",[132,2222,143],{"class":142},[132,2224,2225,2227,2229],{"class":134,"line":1029},[132,2226,2049],{"class":138},[132,2228,160],{"class":142},[132,2230,1991],{"class":163},[132,2232,2233,2235],{"class":134,"line":1034},[132,2234,2058],{"class":138},[132,2236,143],{"class":142},[132,2238,2239,2241],{"class":134,"line":1039},[132,2240,2065],{"class":142},[132,2242,2243],{"class":163},"npx sass-lint '**/*.scss' -v\n",[132,2245,2246,2248],{"class":134,"line":1044},[132,2247,2126],{"class":138},[132,2249,143],{"class":142},[132,2251,2252,2254],{"class":134,"line":1055},[132,2253,2065],{"class":142},[132,2255,2135],{"class":163},[132,2257,2258],{"class":134,"line":1079},[132,2259,279],{"emptyLinePlaceholder":278},[132,2261,2262],{"class":134,"line":1110},[132,2263,2264],{"class":458},"## A job that deploys a review app to a dedicated server running Nginx.\n",[132,2266,2267,2270],{"class":134,"line":1115},[132,2268,2269],{"class":138},"review",[132,2271,143],{"class":142},[132,2273,2274,2276,2278],{"class":134,"line":1133},[132,2275,2049],{"class":138},[132,2277,160],{"class":142},[132,2279,1998],{"class":163},[132,2281,2282,2285],{"class":134,"line":1138},[132,2283,2284],{"class":138},"  variables",[132,2286,143],{"class":142},[132,2288,2289,2292,2294],{"class":134,"line":1178},[132,2290,2291],{"class":138},"    GIT_STRATEGY",[132,2293,160],{"class":142},[132,2295,2296],{"class":163},"none\n",[132,2298,2299,2302],{"class":134,"line":1202},[132,2300,2301],{"class":138},"  before_script",[132,2303,2304],{"class":142},": []\n",[132,2306,2307,2310],{"class":134,"line":1226},[132,2308,2309],{"class":138},"  cache",[132,2311,2312],{"class":142},": {}\n",[132,2314,2315,2317],{"class":134,"line":1240},[132,2316,2058],{"class":138},[132,2318,143],{"class":142},[132,2320,2321,2323],{"class":134,"line":1245},[132,2322,2065],{"class":142},[132,2324,2325],{"class":163},"rsync -av --delete public /srv/nginx/pages/$CI_BUILD_REF_NAME\n",[132,2327,2329,2332],{"class":134,"line":2328},62,[132,2330,2331],{"class":138},"  environment",[132,2333,143],{"class":142},[132,2335,2337,2340,2342],{"class":134,"line":2336},63,[132,2338,2339],{"class":138},"    name",[132,2341,160],{"class":142},[132,2343,2344],{"class":163},"review/$CI_BUILD_REF_NAME\n",[132,2346,2348,2351,2353],{"class":134,"line":2347},64,[132,2349,2350],{"class":138},"    url",[132,2352,160],{"class":142},[132,2354,2355],{"class":163},"http://$CI_BUILD_REF_NAME.$APPS_DOMAIN\n",[132,2357,2359,2362,2364],{"class":134,"line":2358},65,[132,2360,2361],{"class":138},"    on_stop",[132,2363,160],{"class":142},[132,2365,2366],{"class":163},"review_stop\n",[132,2368,2370,2373],{"class":134,"line":2369},66,[132,2371,2372],{"class":138},"  only",[132,2374,143],{"class":142},[132,2376,2378,2380],{"class":134,"line":2377},67,[132,2379,2065],{"class":142},[132,2381,2382],{"class":163},"branches@gitlab-org/gitlab-docs\n",[132,2384,2386,2388],{"class":134,"line":2385},68,[132,2387,2112],{"class":138},[132,2389,143],{"class":142},[132,2391,2393,2395],{"class":134,"line":2392},69,[132,2394,2065],{"class":142},[132,2396,2121],{"class":163},[132,2398,2400,2402],{"class":134,"line":2399},70,[132,2401,2126],{"class":138},[132,2403,143],{"class":142},[132,2405,2407,2409],{"class":134,"line":2406},71,[132,2408,2065],{"class":142},[132,2410,2411],{"class":163},"nginx\n",[132,2413,2415,2417],{"class":134,"line":2414},72,[132,2416,2065],{"class":142},[132,2418,2419],{"class":163},"review-apps\n",[132,2421,2423],{"class":134,"line":2422},73,[132,2424,279],{"emptyLinePlaceholder":278},[132,2426,2428],{"class":134,"line":2427},74,[132,2429,2430],{"class":458},"## Stop the review app\n",[132,2432,2434,2437],{"class":134,"line":2433},75,[132,2435,2436],{"class":138},"review_stop",[132,2438,143],{"class":142},[132,2440,2442,2444,2446],{"class":134,"line":2441},76,[132,2443,2049],{"class":138},[132,2445,160],{"class":142},[132,2447,1998],{"class":163},[132,2449,2451,2453],{"class":134,"line":2450},77,[132,2452,2284],{"class":138},[132,2454,143],{"class":142},[132,2456,2458,2460,2462],{"class":134,"line":2457},78,[132,2459,2291],{"class":138},[132,2461,160],{"class":142},[132,2463,2296],{"class":163},[132,2465,2467,2469],{"class":134,"line":2466},79,[132,2468,2301],{"class":138},[132,2470,2304],{"class":142},[132,2472,2474,2476],{"class":134,"line":2473},80,[132,2475,2080],{"class":138},[132,2477,2312],{"class":142},[132,2479,2481,2483],{"class":134,"line":2480},81,[132,2482,2309],{"class":138},[132,2484,2312],{"class":142},[132,2486,2488,2491],{"class":134,"line":2487},82,[132,2489,2490],{"class":138},"  dependencies",[132,2492,2304],{"class":142},[132,2494,2496,2498],{"class":134,"line":2495},83,[132,2497,2058],{"class":138},[132,2499,143],{"class":142},[132,2501,2503,2505],{"class":134,"line":2502},84,[132,2504,2065],{"class":142},[132,2506,2507],{"class":163},"rm -rf public /srv/nginx/pages/$CI_BUILD_REF_NAME\n",[132,2509,2511,2514,2516],{"class":134,"line":2510},85,[132,2512,2513],{"class":138},"  when",[132,2515,160],{"class":142},[132,2517,2518],{"class":163},"manual\n",[132,2520,2522,2524],{"class":134,"line":2521},86,[132,2523,2331],{"class":138},[132,2525,143],{"class":142},[132,2527,2529,2531,2533],{"class":134,"line":2528},87,[132,2530,2339],{"class":138},[132,2532,160],{"class":142},[132,2534,2344],{"class":163},[132,2536,2538,2541,2543],{"class":134,"line":2537},88,[132,2539,2540],{"class":138},"    action",[132,2542,160],{"class":142},[132,2544,2545],{"class":163},"stop\n",[132,2547,2549,2551],{"class":134,"line":2548},89,[132,2550,2372],{"class":138},[132,2552,143],{"class":142},[132,2554,2556,2558],{"class":134,"line":2555},90,[132,2557,2065],{"class":142},[132,2559,2382],{"class":163},[132,2561,2563,2565],{"class":134,"line":2562},91,[132,2564,2112],{"class":138},[132,2566,143],{"class":142},[132,2568,2570,2572],{"class":134,"line":2569},92,[132,2571,2065],{"class":142},[132,2573,2121],{"class":163},[132,2575,2577,2579],{"class":134,"line":2576},93,[132,2578,2126],{"class":138},[132,2580,143],{"class":142},[132,2582,2584,2586],{"class":134,"line":2583},94,[132,2585,2065],{"class":142},[132,2587,2411],{"class":163},[132,2589,2591,2593],{"class":134,"line":2590},95,[132,2592,2065],{"class":142},[132,2594,2419],{"class":163},[132,2596,2598],{"class":134,"line":2597},96,[132,2599,279],{"emptyLinePlaceholder":278},[132,2601,2603],{"class":134,"line":2602},97,[132,2604,2605],{"class":458},"## Deploy the static site to GitLab Pages\n",[132,2607,2609,2612],{"class":134,"line":2608},98,[132,2610,2611],{"class":138},"pages",[132,2613,143],{"class":142},[132,2615,2617,2619,2621],{"class":134,"line":2616},99,[132,2618,2049],{"class":138},[132,2620,160],{"class":142},[132,2622,1998],{"class":163},[132,2624,2626,2628],{"class":134,"line":2625},100,[132,2627,2331],{"class":138},[132,2629,143],{"class":142},[132,2631,2633,2635,2637],{"class":134,"line":2632},101,[132,2634,2339],{"class":138},[132,2636,160],{"class":142},[132,2638,2639],{"class":163},"production\n",[132,2641,2643,2645,2647],{"class":134,"line":2642},102,[132,2644,2350],{"class":138},[132,2646,160],{"class":142},[132,2648,2649],{"class":163},"https://docs.gitlab.com\n",[132,2651,2653,2655],{"class":134,"line":2652},103,[132,2654,2058],{"class":138},[132,2656,143],{"class":142},[132,2658,2660,2662],{"class":134,"line":2659},104,[132,2661,2065],{"class":142},[132,2663,2068],{"class":163},[132,2665,2667,2669],{"class":134,"line":2666},105,[132,2668,2065],{"class":142},[132,2670,2075],{"class":163},[132,2672,2674],{"class":134,"line":2673},106,[132,2675,2676],{"class":458},"    # Symlink all README.html to index.html\n",[132,2678,2680,2682],{"class":134,"line":2679},107,[132,2681,2065],{"class":142},[132,2683,2684],{"class":163},"for i in `find public -name README.html`; do ln -sf README.html $(dirname $i)/index.html; done\n",[132,2686,2688,2690],{"class":134,"line":2687},108,[132,2689,2080],{"class":138},[132,2691,143],{"class":142},[132,2693,2695,2697],{"class":134,"line":2694},109,[132,2696,2087],{"class":138},[132,2698,143],{"class":142},[132,2700,2702,2704],{"class":134,"line":2701},110,[132,2703,2065],{"class":142},[132,2705,2097],{"class":163},[132,2707,2709,2711,2713],{"class":134,"line":2708},111,[132,2710,2102],{"class":138},[132,2712,160],{"class":142},[132,2714,2715],{"class":163},"1h\n",[132,2717,2719,2721],{"class":134,"line":2718},112,[132,2720,2372],{"class":138},[132,2722,143],{"class":142},[132,2724,2726,2728],{"class":134,"line":2725},113,[132,2727,2065],{"class":142},[132,2729,2730],{"class":163},"master@gitlab-org/gitlab-docs\n",[132,2732,2734,2736],{"class":134,"line":2733},114,[132,2735,2126],{"class":138},[132,2737,143],{"class":142},[132,2739,2741,2743],{"class":134,"line":2740},115,[132,2742,2065],{"class":142},[132,2744,2135],{"class":163},[12,2746,2747,2748,70],{},"To better visualize how the jobs are run, take a look at how the pipeline\ngraph looks like for ",[16,2749,2752],{"href":2750,"rel":2751},"https://gitlab.com/gitlab-org/gitlab-docs/pipelines/5266794",[],"one of the pipelines",[12,2754,2755],{},[2756,2757],"img",{"alt":2758,"src":2759},"Pipeline graph example","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398755/blog/Content%20Images/new-gitlab-docs-site/pipeline-graph.png",[12,2761,2762],{},"Let's see what all these settings mean.",[12,2764,2765,2766,70],{},"For more information, you can read the ",[16,2767,2770,2771],{"href":2768,"rel":2769},"https://docs.gitlab.com/ci/yaml/",[],"documentation on ",[58,2772,1911],{},[2774,2775],"hr",{},[12,2777,2778],{},"Define the Docker image to be used:",[123,2780,2782],{"className":125,"code":2781,"language":127,"meta":128,"style":128},"image: ruby:2.3\n",[58,2783,2784],{"__ignoreMap":128},[132,2785,2786,2788,2790],{"class":134,"line":135},[132,2787,1922],{"class":138},[132,2789,160],{"class":142},[132,2791,1927],{"class":163},[12,2793,2794,2797],{},[132,2795,2796],{},"Cache"," the vendor/ruby directory so that we don't have to install the\ngems for each job/pipeline:",[123,2799,2801],{"className":125,"code":2800,"language":127,"meta":128,"style":128},"cache:\n  key: \"ruby-231\"\n  paths:\n  - vendor/ruby\n\n",[58,2802,2803,2809,2817,2823],{"__ignoreMap":128},[132,2804,2805,2807],{"class":134,"line":135},[132,2806,1941],{"class":138},[132,2808,143],{"class":142},[132,2810,2811,2813,2815],{"class":134,"line":146},[132,2812,1948],{"class":138},[132,2814,160],{"class":142},[132,2816,1953],{"class":163},[132,2818,2819,2821],{"class":134,"line":154},[132,2820,1958],{"class":138},[132,2822,143],{"class":142},[132,2824,2825,2827],{"class":134,"line":167},[132,2826,1965],{"class":142},[132,2828,1968],{"class":163},[12,2830,2831,2832,2834],{},"Define the ",[132,2833,1982],{}," the jobs will run:",[123,2836,2838],{"className":125,"code":2837,"language":127,"meta":128,"style":128},"stages:\n  - test\n  - deploy\n\n",[58,2839,2840,2846,2852],{"__ignoreMap":128},[132,2841,2842,2844],{"class":134,"line":135},[132,2843,1982],{"class":138},[132,2845,143],{"class":142},[132,2847,2848,2850],{"class":134,"line":146},[132,2849,1965],{"class":142},[132,2851,1991],{"class":163},[132,2853,2854,2856],{"class":134,"line":154},[132,2855,1965],{"class":142},[132,2857,1998],{"class":163},[12,2859,2860,2861,2867],{},"Before each job's script is run, run the commands that are defined in the\n",[16,2862,2865],{"href":2863,"rel":2864},"https://docs.gitlab.com/ci/yaml/#before_script",[],[58,2866,2012],{},". Display the Ruby version and install\nthe needed gems:",[123,2869,2871],{"className":125,"code":2870,"language":127,"meta":128,"style":128},"before_script:\n  - ruby -v\n  - bundle install --jobs 4 --path vendor\n\n",[58,2872,2873,2879,2885],{"__ignoreMap":128},[132,2874,2875,2877],{"class":134,"line":135},[132,2876,2012],{"class":138},[132,2878,143],{"class":142},[132,2880,2881,2883],{"class":134,"line":146},[132,2882,1965],{"class":142},[132,2884,2021],{"class":163},[132,2886,2887,2889],{"class":134,"line":154},[132,2888,1965],{"class":142},[132,2890,2028],{"class":163},[12,2892,2893,2894,2896,2897,2899,2900,2903,2904,2907],{},"In the ",[58,2895,2042],{}," job we make sure the site builds successfully.\nIt first pulls the repos locally, then runs ",[58,2898,1759],{}," to compile the site.\nThe ",[58,2901,2902],{},"public/"," directory where the static site is built, is uploaded as\nan artifact so that it can pass between stages. We define an expire date of\none week. The job runs on all refs except master. The ",[58,2905,2906],{},"docker"," tag ensures that\nthis job is picked by the shared Runners on GitLab.com:",[123,2909,2911],{"className":125,"code":2910,"language":127,"meta":128,"style":128},"verify_compile:\n  stage: test\n  script:\n    - rake pull_repos\n    - nanoc\n  artifacts:\n    paths:\n      - public\n    expire_in: 1w\n  except:\n    - master\n  tags:\n    - docker\n\n",[58,2912,2913,2919,2927,2933,2939,2945,2951,2957,2963,2971,2977,2983,2989],{"__ignoreMap":128},[132,2914,2915,2917],{"class":134,"line":135},[132,2916,2042],{"class":138},[132,2918,143],{"class":142},[132,2920,2921,2923,2925],{"class":134,"line":146},[132,2922,2049],{"class":138},[132,2924,160],{"class":142},[132,2926,1991],{"class":163},[132,2928,2929,2931],{"class":134,"line":154},[132,2930,2058],{"class":138},[132,2932,143],{"class":142},[132,2934,2935,2937],{"class":134,"line":167},[132,2936,2065],{"class":142},[132,2938,2068],{"class":163},[132,2940,2941,2943],{"class":134,"line":178},[132,2942,2065],{"class":142},[132,2944,2075],{"class":163},[132,2946,2947,2949],{"class":134,"line":189},[132,2948,2080],{"class":138},[132,2950,143],{"class":142},[132,2952,2953,2955],{"class":134,"line":200},[132,2954,2087],{"class":138},[132,2956,143],{"class":142},[132,2958,2959,2961],{"class":134,"line":211},[132,2960,2094],{"class":142},[132,2962,2097],{"class":163},[132,2964,2965,2967,2969],{"class":134,"line":222},[132,2966,2102],{"class":138},[132,2968,160],{"class":142},[132,2970,2107],{"class":163},[132,2972,2973,2975],{"class":134,"line":233},[132,2974,2112],{"class":138},[132,2976,143],{"class":142},[132,2978,2979,2981],{"class":134,"line":241},[132,2980,2065],{"class":142},[132,2982,2121],{"class":163},[132,2984,2985,2987],{"class":134,"line":252},[132,2986,2126],{"class":138},[132,2988,143],{"class":142},[132,2990,2991,2993],{"class":134,"line":263},[132,2992,2065],{"class":142},[132,2994,2135],{"class":163},[12,2996,2893,2997,2999,3000,3002],{},[58,2998,2149],{}," job we check for dead internal links using Nanoc's\nbuilt-in functionality. We first need to pull the repos and compile the static\nsite. We allow it to fail since the source of the dead links are in a\ndifferent repository, not much related with the current one.\nThe ",[58,3001,2906],{}," tag ensures that this job is picked by the shared Runners\non GitLab.com:",[123,3004,3006],{"className":125,"code":3005,"language":127,"meta":128,"style":128},"internal_links:\n  stage: test\n  script:\n    - rake pull_repos\n    - nanoc\n    - nanoc check internal_links\n  allow_failure: true\n  tags:\n    - docker\n\n",[58,3007,3008,3014,3022,3028,3034,3040,3046,3054,3060],{"__ignoreMap":128},[132,3009,3010,3012],{"class":134,"line":135},[132,3011,2149],{"class":138},[132,3013,143],{"class":142},[132,3015,3016,3018,3020],{"class":134,"line":146},[132,3017,2049],{"class":138},[132,3019,160],{"class":142},[132,3021,1991],{"class":163},[132,3023,3024,3026],{"class":134,"line":154},[132,3025,2058],{"class":138},[132,3027,143],{"class":142},[132,3029,3030,3032],{"class":134,"line":167},[132,3031,2065],{"class":142},[132,3033,2068],{"class":163},[132,3035,3036,3038],{"class":134,"line":178},[132,3037,2065],{"class":142},[132,3039,2075],{"class":163},[132,3041,3042,3044],{"class":134,"line":189},[132,3043,2065],{"class":142},[132,3045,2184],{"class":163},[132,3047,3048,3050,3052],{"class":134,"line":200},[132,3049,2189],{"class":138},[132,3051,160],{"class":142},[132,3053,2194],{"class":436},[132,3055,3056,3058],{"class":134,"line":211},[132,3057,2126],{"class":138},[132,3059,143],{"class":142},[132,3061,3062,3064],{"class":134,"line":222},[132,3063,2065],{"class":142},[132,3065,2135],{"class":163},[12,3067,1251,3068,3070,3071,3073],{},[58,3069,2220],{}," job makes sure our SCSS stylesheets are correctly defined by\nrunning a linter on them. The ",[58,3072,2906],{}," tag ensures that this job is picked by\nthe shared Runners on GitLab.com:",[123,3075,3077],{"className":125,"code":3076,"language":127,"meta":128,"style":128},"scss_lint:\n  stage: test\n  script:\n    - npx sass-lint '**/*.scss' -v\n  tags:\n    - docker\n\n",[58,3078,3079,3085,3093,3099,3105,3111],{"__ignoreMap":128},[132,3080,3081,3083],{"class":134,"line":135},[132,3082,2220],{"class":138},[132,3084,143],{"class":142},[132,3086,3087,3089,3091],{"class":134,"line":146},[132,3088,2049],{"class":138},[132,3090,160],{"class":142},[132,3092,1991],{"class":163},[132,3094,3095,3097],{"class":134,"line":154},[132,3096,2058],{"class":138},[132,3098,143],{"class":142},[132,3100,3101,3103],{"class":134,"line":167},[132,3102,2065],{"class":142},[132,3104,2243],{"class":163},[132,3106,3107,3109],{"class":134,"line":178},[132,3108,2126],{"class":138},[132,3110,143],{"class":142},[132,3112,3113,3115],{"class":134,"line":189},[132,3114,2065],{"class":142},[132,3116,2135],{"class":163},[12,3118,3119],{},"Next, we define the Review Apps.",[1873,3121,1864],{"id":3122},"review-apps",[12,3124,3125,3126,3129],{},"When opening a merge request for the docs site we use a new feature called ",[16,3127,1864],{"href":1862,"rel":3128},[]," to test changes. This lets us test new features, style changes, new sections, etc., by deploying the updated static site to a test domain. On every merge request that all jobs finished successfully, we can see a link with the URL to the temporary deployed docs site.",[12,3131,3132],{},[2756,3133],{"alt":3134,"src":3135},"Review apps","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398360/blog/Content%20Images/gitlab-docs-review-apps-screenshot.png",[12,3137,3138,3139,1552],{},"We define two additional jobs for that purpose in ",[58,3140,1911],{},[123,3142,3144],{"className":125,"code":3143,"language":127,"meta":128,"style":128},"review:\n  stage: deploy\n  variables:\n    GIT_STRATEGY: none\n  before_script: []\n  cache: {}\n  script:\n    - rsync -av --delete public /srv/nginx/pages/$CI_BUILD_REF_NAME\n  environment:\n    name: review/$CI_BUILD_REF_NAME\n    url: http://$CI_BUILD_REF_NAME.$APPS_DOMAIN\n    on_stop: review_stop\n  only:\n    - branches@gitlab-org/gitlab-docs\n  except:\n    - master\n  tags:\n    - nginx\n    - review-apps\n\nreview_stop:\n  stage: deploy\n  variables:\n    GIT_STRATEGY: none\n  before_script: []\n  artifacts: {}\n  cache: {}\n  dependencies: []\n  script:\n    - rm -rf public /srv/nginx/pages/$CI_BUILD_REF_NAME\n  when: manual\n  environment:\n    name: review/$CI_BUILD_REF_NAME\n    action: stop\n  only:\n    - branches@gitlab-org/gitlab-docs\n  except:\n    - master\n  tags:\n    - nginx\n    - review-apps\n\n",[58,3145,3146,3152,3160,3166,3174,3180,3186,3192,3198,3204,3212,3220,3228,3234,3240,3246,3252,3258,3264,3270,3274,3280,3288,3294,3302,3308,3314,3320,3326,3332,3338,3346,3352,3360,3368,3374,3380,3386,3392,3398,3404],{"__ignoreMap":128},[132,3147,3148,3150],{"class":134,"line":135},[132,3149,2269],{"class":138},[132,3151,143],{"class":142},[132,3153,3154,3156,3158],{"class":134,"line":146},[132,3155,2049],{"class":138},[132,3157,160],{"class":142},[132,3159,1998],{"class":163},[132,3161,3162,3164],{"class":134,"line":154},[132,3163,2284],{"class":138},[132,3165,143],{"class":142},[132,3167,3168,3170,3172],{"class":134,"line":167},[132,3169,2291],{"class":138},[132,3171,160],{"class":142},[132,3173,2296],{"class":163},[132,3175,3176,3178],{"class":134,"line":178},[132,3177,2301],{"class":138},[132,3179,2304],{"class":142},[132,3181,3182,3184],{"class":134,"line":189},[132,3183,2309],{"class":138},[132,3185,2312],{"class":142},[132,3187,3188,3190],{"class":134,"line":200},[132,3189,2058],{"class":138},[132,3191,143],{"class":142},[132,3193,3194,3196],{"class":134,"line":211},[132,3195,2065],{"class":142},[132,3197,2325],{"class":163},[132,3199,3200,3202],{"class":134,"line":222},[132,3201,2331],{"class":138},[132,3203,143],{"class":142},[132,3205,3206,3208,3210],{"class":134,"line":233},[132,3207,2339],{"class":138},[132,3209,160],{"class":142},[132,3211,2344],{"class":163},[132,3213,3214,3216,3218],{"class":134,"line":241},[132,3215,2350],{"class":138},[132,3217,160],{"class":142},[132,3219,2355],{"class":163},[132,3221,3222,3224,3226],{"class":134,"line":252},[132,3223,2361],{"class":138},[132,3225,160],{"class":142},[132,3227,2366],{"class":163},[132,3229,3230,3232],{"class":134,"line":263},[132,3231,2372],{"class":138},[132,3233,143],{"class":142},[132,3235,3236,3238],{"class":134,"line":275},[132,3237,2065],{"class":142},[132,3239,2382],{"class":163},[132,3241,3242,3244],{"class":134,"line":282},[132,3243,2112],{"class":138},[132,3245,143],{"class":142},[132,3247,3248,3250],{"class":134,"line":289},[132,3249,2065],{"class":142},[132,3251,2121],{"class":163},[132,3253,3254,3256],{"class":134,"line":294},[132,3255,2126],{"class":138},[132,3257,143],{"class":142},[132,3259,3260,3262],{"class":134,"line":302},[132,3261,2065],{"class":142},[132,3263,2411],{"class":163},[132,3265,3266,3268],{"class":134,"line":312},[132,3267,2065],{"class":142},[132,3269,2419],{"class":163},[132,3271,3272],{"class":134,"line":322},[132,3273,279],{"emptyLinePlaceholder":278},[132,3275,3276,3278],{"class":134,"line":332},[132,3277,2436],{"class":138},[132,3279,143],{"class":142},[132,3281,3282,3284,3286],{"class":134,"line":342},[132,3283,2049],{"class":138},[132,3285,160],{"class":142},[132,3287,1998],{"class":163},[132,3289,3290,3292],{"class":134,"line":352},[132,3291,2284],{"class":138},[132,3293,143],{"class":142},[132,3295,3296,3298,3300],{"class":134,"line":362},[132,3297,2291],{"class":138},[132,3299,160],{"class":142},[132,3301,2296],{"class":163},[132,3303,3304,3306],{"class":134,"line":372},[132,3305,2301],{"class":138},[132,3307,2304],{"class":142},[132,3309,3310,3312],{"class":134,"line":379},[132,3311,2080],{"class":138},[132,3313,2312],{"class":142},[132,3315,3316,3318],{"class":134,"line":389},[132,3317,2309],{"class":138},[132,3319,2312],{"class":142},[132,3321,3322,3324],{"class":134,"line":399},[132,3323,2490],{"class":138},[132,3325,2304],{"class":142},[132,3327,3328,3330],{"class":134,"line":762},[132,3329,2058],{"class":138},[132,3331,143],{"class":142},[132,3333,3334,3336],{"class":134,"line":767},[132,3335,2065],{"class":142},[132,3337,2507],{"class":163},[132,3339,3340,3342,3344],{"class":134,"line":779},[132,3341,2513],{"class":138},[132,3343,160],{"class":142},[132,3345,2518],{"class":163},[132,3347,3348,3350],{"class":134,"line":805},[132,3349,2331],{"class":138},[132,3351,143],{"class":142},[132,3353,3354,3356,3358],{"class":134,"line":848},[132,3355,2339],{"class":138},[132,3357,160],{"class":142},[132,3359,2344],{"class":163},[132,3361,3362,3364,3366],{"class":134,"line":854},[132,3363,2540],{"class":138},[132,3365,160],{"class":142},[132,3367,2545],{"class":163},[132,3369,3370,3372],{"class":134,"line":860},[132,3371,2372],{"class":138},[132,3373,143],{"class":142},[132,3375,3376,3378],{"class":134,"line":871},[132,3377,2065],{"class":142},[132,3379,2382],{"class":163},[132,3381,3382,3384],{"class":134,"line":891},[132,3383,2112],{"class":138},[132,3385,143],{"class":142},[132,3387,3388,3390],{"class":134,"line":905},[132,3389,2065],{"class":142},[132,3391,2121],{"class":163},[132,3393,3394,3396],{"class":134,"line":917},[132,3395,2126],{"class":138},[132,3397,143],{"class":142},[132,3399,3400,3402],{"class":134,"line":923},[132,3401,2065],{"class":142},[132,3403,2411],{"class":163},[132,3405,3406,3408],{"class":134,"line":928},[132,3407,2065],{"class":142},[132,3409,2419],{"class":163},[12,3411,3412,3413,3415,3416,3418,3419,3422,3423,3425,3426,3431,3432,3434,3435,3437],{},"They both run on all branches except ",[58,3414,1852],{}," since ",[58,3417,1852],{}," is deployed straight\nto production. Once someone with write access to the repository pushes a branch\nand creates a merge request, if the jobs in the ",[58,3420,3421],{},"test"," stage finish successfully,\nthe ",[58,3424,2269],{}," job deploys the code of that particular branch to a server. The\nserver is set up to ",[16,3427,3430],{"href":3428,"rel":3429},"https://gitlab.com/gitlab-examples/review-apps-nginx",[],"use Nginx with Review Apps",", and it uses\nthe artifacts from the previously ",[58,3433,2042],{}," job which contain the\n",[58,3436,2902],{}," directory with the HTML files Nanoc compiled.",[12,3439,3440,3441,3446,3447,3450,3451,70],{},"Notice that both jobs rely on ",[16,3442,3445],{"href":3443,"rel":3444},"https://docs.gitlab.com/ci/environments/#dynamic-environments",[],"dynamic environments"," and with\nthe ",[58,3448,3449],{},"review/"," prefix we can group them under the ",[16,3452,3455],{"href":3453,"rel":3454},"https://gitlab.com/gitlab-org/gitlab-docs/environments",[],"Environments page",[12,3457,1251,3458,3460,3461,3463],{},[58,3459,2436],{}," job depends on the ",[58,3462,2269],{}," one and is called whenever we\nwant to clear up the review app. By default it is called every time the related\nbranch is deleted, but you can also manually call it with the buttons that can\nbe found in GitLab.",[12,3465,3466,3467,3470,3471,3474],{},"The trick of this particular set up is that we use the shared Runners provided\nin GitLab.com to test and build the docs site (using Docker containers) whereas\nwe use a specific Runner that is set up in the server that hosts the Review Apps\nand is configured with the ",[132,3468,3469],{},"shell executor",". GitLab CI knows what Runner to use\neach time from the ",[58,3472,3473],{},"tags"," we provide each job with.",[12,3476,1251,3477,3479],{},[58,3478,2269],{}," job has also some other things specified:",[123,3481,3483],{"className":125,"code":3482,"language":127,"meta":128,"style":128},"variables:\n  GIT_STRATEGY: none\nbefore_script: []\ncache: {}\n",[58,3484,3485,3492,3501,3507],{"__ignoreMap":128},[132,3486,3487,3490],{"class":134,"line":135},[132,3488,3489],{"class":138},"variables",[132,3491,143],{"class":142},[132,3493,3494,3497,3499],{"class":134,"line":146},[132,3495,3496],{"class":138},"  GIT_STRATEGY",[132,3498,160],{"class":142},[132,3500,2296],{"class":163},[132,3502,3503,3505],{"class":134,"line":154},[132,3504,2012],{"class":138},[132,3506,2304],{"class":142},[132,3508,3509,3511],{"class":134,"line":167},[132,3510,1941],{"class":138},[132,3512,2312],{"class":142},[12,3514,3515,3516,3523,3524,3527,3528,3531,3532,3534,3535,3537],{},"In this case, ",[16,3517,3520],{"href":3518,"rel":3519},"https://docs.gitlab.com/ci/runners/configure_runners/#git-strategy",[],[58,3521,3522],{},"GIT_STRATEGY"," is set up to ",[58,3525,3526],{},"none"," since we don't need to\ncheckout the repository for this job. We only use ",[58,3529,3530],{},"rsync"," to copy over the\nartifacts that were passed from the previous job to the server where Review\nApps are deployed. We also turn off the ",[58,3533,2012],{}," since we don't need it\nto run, same for ",[58,3536,1941],{},". They both are defined globally, so you need to pass\nan empty array and hash respectively to disable them in a job level.",[12,3539,3540,3541,1548,3543,3545,3546,3548],{},"On the other hand, setting the ",[58,3542,3522],{},[58,3544,3526],{}," is necessary on the\n",[58,3547,2436],{}," job so that the GitLab Runner won't try to checkout the code after\nthe branch is deleted. We also define one additional thing in it:",[123,3550,3552],{"className":125,"code":3551,"language":127,"meta":128,"style":128},"dependencies: []\n",[58,3553,3554],{"__ignoreMap":128},[132,3555,3556,3559],{"class":134,"line":135},[132,3557,3558],{"class":138},"dependencies",[132,3560,2304],{"class":142},[12,3562,3563,3564,70],{},"Since this is the last job that is performed in the lifecycle of a merge request\n(after it's merged and the branch deleted), we opt to not download any artifacts\nfrom the previous stage with passing an empty array in ",[16,3565,3568],{"href":3566,"rel":3567},"https://docs.gitlab.com/ci/yaml/#dependencies",[],[58,3569,3558],{},[2774,3571],{},[12,3573,3574,3575,3579,3580,3584,3585,3589],{},"See ",[16,3576,3578],{"href":3577},"/blog/introducing-review-apps/","our blog post on Review Apps"," for\nmore information about how they work and their purpose. Be sure to also check\nthe ",[16,3581,3583],{"href":1862,"rel":3582},[],"Review Apps documentation"," as well as ",[16,3586,3588],{"href":3443,"rel":3587},[],"how dynamic environments work","\nsince they are the basis of the Review Apps.",[12,3591,3592,3593,3596,3597,70],{},"The final step after the site gets successfully built is to deploy to\nproduction which is under the URL everybody knows: ",[16,3594,18],{"href":18,"rel":3595},[],".\nFor that purpose, we use ",[16,3598,34],{"href":32,"rel":3599},[],[1873,3601,34],{"id":3602},"gitlab-pages",[12,3604,3605,3609,3610,3615,3616,1859,3621,1859,3626,1859,3631,3636],{},[16,3606,34],{"href":3607,"rel":3608},"https://pages.gitlab.io/",[]," hosts ",[16,3611,3614],{"href":3612,"rel":3613},"https://en.wikipedia.org/wiki/Static_web_page",[],"static websites"," and can be used with any Static Site Generator, including ",[16,3617,3620],{"href":3618,"rel":3619},"https://jekyllrb.com/",[],"Jekyll",[16,3622,3625],{"href":3623,"rel":3624},"https://gohugo.io/",[],"Hugo",[16,3627,3630],{"href":3628,"rel":3629},"https://middlemanapp.com/",[],"Middleman",[16,3632,3635],{"href":3633,"rel":3634},"http://blog.getpelican.com/",[],"Pelican",", and of course Nanoc.",[12,3638,3639,3640,3643,3644,70],{},"GitLab Pages allows us to create the static site dynamically since it just deploys the ",[58,3641,3642],{},"public"," directory after the GitLab CI task is done. The job responsible for this is named ",[58,3645,2611],{},[12,3647,3648,3649,3651,3652,3654,3655,3657],{},"A production environment is set with a url to the of the docs portal.\nThe script pulls the repos, runs ",[58,3650,1759],{}," to compile the static site.\nThe ",[58,3653,2902],{}," directory where the static site is built, is uploaded as\nan artifact so that it can be deployed to GitLab Pages. We define an expire\ndate of one hour and the job runs only on the master branch.\nThe ",[58,3656,2906],{}," tag ensures that this job is picked by the shared Runners\non GitLab.com.",[123,3659,3661],{"className":125,"code":3660,"language":127,"meta":128,"style":128},"pages:\n  stage: deploy\n  environment:\n    name: production\n    url: https://docs.gitlab.com\n  script:\n    - rake pull_repos\n    - nanoc\n    # Symlink all README.html to index.html\n    - for i in `find public -name README.html`; do ln -sf README.html $(dirname $i)/index.html; done\n  artifacts:\n    paths:\n    - public\n    expire_in: 1h\n  only:\n    - master@gitlab-org/gitlab-docs\n  tags:\n    - docker\n\n",[58,3662,3663,3669,3677,3683,3691,3699,3705,3711,3717,3721,3727,3733,3739,3745,3753,3759,3765,3771],{"__ignoreMap":128},[132,3664,3665,3667],{"class":134,"line":135},[132,3666,2611],{"class":138},[132,3668,143],{"class":142},[132,3670,3671,3673,3675],{"class":134,"line":146},[132,3672,2049],{"class":138},[132,3674,160],{"class":142},[132,3676,1998],{"class":163},[132,3678,3679,3681],{"class":134,"line":154},[132,3680,2331],{"class":138},[132,3682,143],{"class":142},[132,3684,3685,3687,3689],{"class":134,"line":167},[132,3686,2339],{"class":138},[132,3688,160],{"class":142},[132,3690,2639],{"class":163},[132,3692,3693,3695,3697],{"class":134,"line":178},[132,3694,2350],{"class":138},[132,3696,160],{"class":142},[132,3698,2649],{"class":163},[132,3700,3701,3703],{"class":134,"line":189},[132,3702,2058],{"class":138},[132,3704,143],{"class":142},[132,3706,3707,3709],{"class":134,"line":200},[132,3708,2065],{"class":142},[132,3710,2068],{"class":163},[132,3712,3713,3715],{"class":134,"line":211},[132,3714,2065],{"class":142},[132,3716,2075],{"class":163},[132,3718,3719],{"class":134,"line":222},[132,3720,2676],{"class":458},[132,3722,3723,3725],{"class":134,"line":233},[132,3724,2065],{"class":142},[132,3726,2684],{"class":163},[132,3728,3729,3731],{"class":134,"line":241},[132,3730,2080],{"class":138},[132,3732,143],{"class":142},[132,3734,3735,3737],{"class":134,"line":252},[132,3736,2087],{"class":138},[132,3738,143],{"class":142},[132,3740,3741,3743],{"class":134,"line":263},[132,3742,2065],{"class":142},[132,3744,2097],{"class":163},[132,3746,3747,3749,3751],{"class":134,"line":275},[132,3748,2102],{"class":138},[132,3750,160],{"class":142},[132,3752,2715],{"class":163},[132,3754,3755,3757],{"class":134,"line":282},[132,3756,2372],{"class":138},[132,3758,143],{"class":142},[132,3760,3761,3763],{"class":134,"line":289},[132,3762,2065],{"class":142},[132,3764,2730],{"class":163},[132,3766,3767,3769],{"class":134,"line":294},[132,3768,2126],{"class":138},[132,3770,143],{"class":142},[132,3772,3773,3775],{"class":134,"line":302},[132,3774,2065],{"class":142},[132,3776,2135],{"class":163},[12,3778,3779,3780,3782],{},"GitLab Pages deploys our documentation site whenever a commit is made to the master branch of the gitlab-docs repository and is run only on the ",[58,3781,1852],{}," branch of the gitlab-docs project.",[12,3784,3785,3786,3791,3792,3797,3798,3802],{},"Since the documentation content itself is not hosted under the gitlab-docs repository, we rely to a CI job under all the products we build the docs site from. We specifically ",[16,3787,3790],{"href":3788,"rel":3789},"https://docs.gitlab.com/ci/triggers/",[],"make use of triggers"," where a build for the docs site is triggered whenever CI runs successfully on the master branches of CE, EE, Omnibus GitLab, or Runner. If you go to the ",[16,3793,3796],{"href":3794,"rel":3795},"https://gitlab.com/gitlab-org/gitlab-docs/pipelines",[],"pipelines page of the gitlab-docs project",", you can notice the ",[3799,3800,3801],"strong",{},"triggered"," word next to the pipelines that are re-run because a trigger was initiated.",[12,3804,3805],{},[2756,3806],{"alt":3807,"src":3808},"Pipeline triggers","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398755/blog/Content%20Images/new-gitlab-docs-site/pipelines-triggers.png",[12,3810,3811,3812,70],{},"How we specifically use triggers for gitlab-docs is briefly described in the\n",[16,3813,3816],{"href":3814,"rel":3815},"https://gitlab.com/gitlab-org/gitlab-docs/blob/master/README.md#deployment-process",[],"project's readme",[12,3818,3819,3820,3823,3824,3827],{},"We also use a hack to symlink all ",[58,3821,3822],{},"README.html"," files into ",[58,3825,3826],{},"index.html"," so that\nthey can be viewed without the extension. Notice how the following links point\nto the same document:",[37,3829,3830,3835],{},[40,3831,3832],{},[16,3833,2768],{"href":2768,"rel":3834},[],[40,3836,3837],{},[16,3838,2768],{"href":2768,"rel":3839},[],[12,3841,3842],{},"The line responsible for this is:",[123,3844,3847],{"className":3845,"code":2684,"language":3846,"meta":128,"style":128},"language-bash shiki shiki-themes github-light","bash",[58,3848,3849],{"__ignoreMap":128},[132,3850,3851,3854,3857,3860,3863,3866,3869,3872,3875,3878,3881,3884,3887,3890,3893,3896,3899,3902,3904],{"class":134,"line":135},[132,3852,3853],{"class":440},"for",[132,3855,3856],{"class":142}," i ",[132,3858,3859],{"class":440},"in",[132,3861,3862],{"class":163}," `",[132,3864,3865],{"class":285},"find",[132,3867,3868],{"class":163}," public ",[132,3870,3871],{"class":436},"-name",[132,3873,3874],{"class":163}," README.html`",[132,3876,3877],{"class":142},"; ",[132,3879,3880],{"class":440},"do",[132,3882,3883],{"class":285}," ln",[132,3885,3886],{"class":436}," -sf",[132,3888,3889],{"class":163}," README.html",[132,3891,3892],{"class":142}," $(",[132,3894,3895],{"class":285},"dirname",[132,3897,3898],{"class":142}," $i)",[132,3900,3901],{"class":163},"/index.html",[132,3903,3877],{"class":142},[132,3905,3906],{"class":440},"done\n",[12,3908,3909,3910,3913],{},"The artifacts are made to ",[132,3911,3912],{},"expire in"," an hour since they are deployed to the\nGitLab Pages server, we don't need them lingering in GitLab forever.",[12,3915,3916,3917,3921],{},"It’s worth noting that GitLab Pages is a ",[16,3918,3920],{"href":3919},"/platform/","GitLab Enterprise Edition","-only feature, but it’s also available for free on GitLab.com.",[44,3923,3925],{"id":3924},"conclusion","Conclusion",[12,3927,3928],{},"Hopefully this shows some GitLab's power and how having everything integrated into one cohesive product simplifies one's workflow. If you have a complex documentation site you’d like to put together from specific directories in multiple Git repositories, the process described above is the best we've been able to come up with. If you have any ideas to make this system better, let us know!",[12,3930,3931,3932,3936],{},"The documentation website is ",[16,3933,3935],{"href":1847,"rel":3934},[],"open source",", available under the MIT License. You’re welcome to take a look at it, submit a merge request, or even fork it to use it with your own project.",[12,3938,3939],{},"Thanks for reading, if you have any questions we’d be happy to answer them in the comments!",[3941,3942,3943],"style",{},"html pre.shiki code .shJU0, html code.shiki .shJU0{--shiki-default:#22863A}html pre.shiki code .sgsFI, html code.shiki .sgsFI{--shiki-default:#24292E}html pre.shiki code .sYBdl, html code.shiki .sYBdl{--shiki-default:#032F62}html pre.shiki code .s7eDp, html code.shiki .s7eDp{--shiki-default:#6F42C1}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 pre.shiki code .sYu0t, html code.shiki .sYu0t{--shiki-default:#005CC5}html pre.shiki code .sD7c4, html code.shiki .sD7c4{--shiki-default:#D73A49}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .sqxcx, html code.shiki .sqxcx{--shiki-default:#E36209}",{"title":128,"searchDepth":146,"depth":146,"links":3945},[3946,3947,3948,3949,3954],{"id":46,"depth":146,"text":47},{"id":91,"depth":146,"text":92},{"id":104,"depth":146,"text":105},{"id":1840,"depth":146,"text":1841,"children":3950},[3951,3952,3953],{"id":1875,"depth":154,"text":28},{"id":3122,"depth":154,"text":1864},{"id":3602,"depth":154,"text":34},{"id":3924,"depth":146,"text":3925},"engineering","2016-12-07","How we built the new GitLab Docs portal from the ground up","md",null,false,"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749666603/Blog/Hero%20Images/book.jpg",{"excerpt":3963},{"type":9,"value":3964},[3965,3970],[12,3966,14,3967,21],{},[16,3968,20],{"href":18,"rel":3969},[],[12,3971,24,3972,29,3974,35],{},[16,3973,28],{"href":27},[16,3975,34],{"href":32,"rel":3976},[],"/en-us/blog/building-a-new-gitlab-docs-site-with-nanoc-gitlab-ci-and-gitlab-pages",{"title":5,"description":3957,"ogTitle":5,"ogDescription":3957,"noIndex":3960,"ogImage":3961,"ogUrl":3979,"ogSiteName":3980,"ogType":3981,"canonicalUrls":3979},"https://about.gitlab.com/blog/building-a-new-gitlab-docs-site-with-nanoc-gitlab-ci-and-gitlab-pages","https://about.gitlab.com","article","building-a-new-gitlab-docs-site-with-nanoc-gitlab-ci-and-gitlab-pages","en-us/blog/building-a-new-gitlab-docs-site-with-nanoc-gitlab-ci-and-gitlab-pages","BlogPost","9Ao8fyvaXLphtdIKD8yuP52GxzrkBFhGLAvFUuxt0ps",{"logo":3987,"freeTrial":3992,"sales":3997,"login":4002,"items":4007,"search":4336,"minimal":4367,"duo":4386,"switchNav":4395,"pricingDeployment":4406},{"config":3988},{"href":3989,"dataGaName":3990,"dataGaLocation":3991},"/","gitlab logo","header",{"text":3993,"config":3994},"Get free trial",{"href":3995,"dataGaName":3996,"dataGaLocation":3991},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com&glm_content=default-saas-trial/","free trial",{"text":3998,"config":3999},"Request a demo",{"href":4000,"dataGaName":4001,"dataGaLocation":3991},"/request-a-demo/","sales",{"text":4003,"config":4004},"Sign in",{"href":4005,"dataGaName":4006,"dataGaLocation":3991},"https://gitlab.com/users/sign_in/","sign in",[4008,4092,4180,4185,4260,4314],{"text":4009,"left":278,"config":4010,"menu":4012},"Platform",{"dataNavLevelOne":4011},"platform",{"type":4013,"link":4014,"columns":4017,"feature":4081},"lists",{"text":4015,"config":4016},"Explore the Platform",{"href":3919,"dataGaName":4011,"dataGaLocation":3991},[4018,4040,4063],{"title":4019,"items":4020},"Execution & Workflows",[4021,4025,4030,4035],{"text":4022,"config":4023},"CI/CD",{"href":27,"dataGaLocation":3991,"dataGaName":4024},"ci/cd",{"text":4026,"config":4027},"Source Code Management",{"href":4028,"dataGaLocation":3991,"dataGaName":4029},"/solutions/source-code-management/","source code management",{"text":4031,"config":4032},"Agile Delivery",{"href":4033,"dataGaLocation":3991,"dataGaName":4034},"/solutions/agile-delivery/","agile delivery",{"text":4036,"config":4037},"Artifact Management",{"href":4038,"dataGaLocation":3991,"dataGaName":4039},"/artifact-central/","artifact management",{"title":4041,"items":4042},"Security & Governance",[4043,4048,4053,4058],{"text":4044,"config":4045},"Application Security Testing",{"href":4046,"dataGaLocation":3991,"dataGaName":4047},"/solutions/application-security-testing/","application security testing",{"text":4049,"config":4050},"Governance & Compliance",{"href":4051,"dataGaLocation":3991,"dataGaName":4052},"/solutions/software-compliance/","governance and compliance",{"text":4054,"config":4055},"Secrets Management",{"href":4056,"dataGaLocation":3991,"dataGaName":4057},"/gitlab-secrets-manager/","secrets management",{"text":4059,"config":4060},"Supply Chain Security",{"href":4061,"dataGaLocation":3991,"dataGaName":4062},"/solutions/supply-chain/","supply chain security",{"title":4064,"items":4065},"Context & AI",[4066,4071,4076],{"text":4067,"config":4068},"Agentic Orchestration",{"href":4069,"dataGaLocation":3991,"dataGaName":4070},"/gitlab-duo-agent-platform/","agentic orchestration",{"text":4072,"config":4073},"Context Graph",{"href":4074,"dataGaLocation":3991,"dataGaName":4075},"/gitlab-orbit/","context graph",{"text":4077,"config":4078},"Visibility & Measurement",{"href":4079,"dataGaLocation":3991,"dataGaName":4080},"/solutions/visibility-measurement/","visibility and measurement",{"config":4082,"title":4085,"text":4086,"link":4087},{"background":4083,"textColor":4084},"url('https://res.cloudinary.com/about-gitlab-com/image/upload/v1790185102/o7dn32anqcb6z8dgd3as.jpg')","#000","Why GitLab","One platform for speed with control across your software lifecycle.",{"text":4088,"config":4089},"Learn more",{"href":4090,"dataGaName":4091,"dataGaLocation":3991},"/why-gitlab/","why gitlab",{"text":4093,"left":278,"config":4094,"menu":4096},"Solutions",{"dataNavLevelOne":4095},"solutions",{"type":4013,"link":4097,"columns":4101,"feature":4171},{"text":4098,"config":4099},"View all Solutions",{"href":4100,"dataGaName":4095,"dataGaLocation":3991},"/solutions/",[4102,4120,4138],{"title":4103,"items":4104},"Outcomes",[4105,4110,4115],{"text":4106,"config":4107},"DevOps Modernization",{"href":4108,"dataGaLocation":3991,"dataGaName":4109},"/assessments/devops-modernization-assessment/","devops modernization",{"text":4111,"config":4112},"Security Modernization",{"href":4113,"dataGaLocation":3991,"dataGaName":4114},"/assessments/security-modernization-assessment/","security modernization",{"text":4116,"config":4117},"AI Modernization",{"href":4118,"dataGaLocation":3991,"dataGaName":4119},"/assessments/ai-modernization-assessment/","ai modernization",{"title":4121,"items":4122},"By size",[4123,4128,4133],{"text":4124,"config":4125},"Enterprise",{"href":4126,"dataGaLocation":3991,"dataGaName":4127},"/enterprise/","enterprise",{"text":4129,"config":4130},"Small Business",{"href":4131,"dataGaLocation":3991,"dataGaName":4132},"/small-business/","small business",{"text":4134,"config":4135},"Startups",{"href":4136,"dataGaLocation":3991,"dataGaName":4137},"/solutions/startups/","startups",{"title":4139,"items":4140},"Industries",[4141,4146,4151,4156,4161,4166],{"text":4142,"config":4143},"Financial Services",{"href":4144,"dataGaLocation":3991,"dataGaName":4145},"/solutions/finance/","financial services",{"text":4147,"config":4148},"Public Sector",{"href":4149,"dataGaLocation":3991,"dataGaName":4150},"/solutions/public-sector/","public sector",{"text":4152,"config":4153},"Telecommunications",{"href":4154,"dataGaLocation":3991,"dataGaName":4155},"/solutions/telecommunications/","telecommunications",{"text":4157,"config":4158},"Automotive",{"href":4159,"dataGaLocation":3991,"dataGaName":4160},"/solutions/automotive/","automotive",{"text":4162,"config":4163},"Education",{"href":4164,"dataGaLocation":3991,"dataGaName":4165},"/solutions/education/","education",{"text":4167,"config":4168},"Aerospace",{"href":4169,"dataGaLocation":3991,"dataGaName":4170},"/solutions/aerospace/","aerospace",{"config":4172,"title":4173,"text":4174,"link":4175},{"background":4083,"textColor":4084},"GitLab Security Standard","Learn how to harden your software factory and let AI agents earn autonomy, without trading away security.",{"text":4176,"config":4177},"Explore the standard",{"href":4178,"dataGaName":4179,"dataGaLocation":3991},"/gitlab-security-standard/","gitlab security standard",{"text":4181,"config":4182},"Pricing",{"href":4183,"dataGaName":4184,"dataGaLocation":3991,"dataNavLevelOne":4184},"/pricing/","pricing",{"text":4186,"config":4187,"menu":4189},"Resources",{"dataNavLevelOne":4188},"resources",{"type":4013,"link":4190,"columns":4194,"feature":4251},{"text":4191,"config":4192},"View all resources",{"href":4193,"dataGaName":4188,"dataGaLocation":3991},"/resources/",[4195,4223],{"title":4196,"items":4197},"Discover",[4198,4203,4208,4213,4218],{"text":4199,"config":4200},"Docs",{"href":4201,"dataGaName":4202,"dataGaLocation":3991},"https://docs.gitlab.com/","docs",{"text":4204,"config":4205},"University",{"href":4206,"dataGaName":4207,"dataGaLocation":3991},"https://university.gitlab.com/","university",{"text":4209,"config":4210},"Demo Series",{"href":4211,"dataGaName":4212,"dataGaLocation":3991},"/gitlab-technical-demo-series/","demo series",{"text":4214,"config":4215},"Demo Hub",{"href":4216,"dataGaName":4217,"dataGaLocation":3991},"/demo-hub/","demo hub",{"text":4219,"config":4220},"Services",{"href":4221,"dataGaName":4222,"dataGaLocation":3991},"/services/","services",{"title":4224,"items":4225},"Connect",[4226,4231,4236,4241,4246],{"text":4227,"config":4228},"Blog",{"href":4229,"dataGaName":4230,"dataGaLocation":3991},"/blog/","blog",{"text":4232,"config":4233},"Community",{"href":4234,"dataGaName":4235,"dataGaLocation":3991},"/community/","community",{"text":4237,"config":4238},"Customers",{"href":4239,"dataGaName":4240,"dataGaLocation":3991},"/customers/","customers",{"text":4242,"config":4243},"Partners",{"href":4244,"dataGaName":4245,"dataGaLocation":3991},"/partners/","partners",{"text":4247,"config":4248},"Events",{"href":4249,"dataGaName":4250,"dataGaLocation":3991},"/events/","events",{"config":4252,"title":4253,"text":4254,"link":4255},{"background":4083,"textColor":4084},"What’s new in GitLab","Stay updated with our latest features and improvements.",{"text":4256,"config":4257},"Read the latest",{"href":4258,"dataGaName":4259,"dataGaLocation":3991},"/whats-new/","whats new",{"text":4261,"config":4262,"menu":4264},"Company",{"dataNavLevelOne":4263},"company",{"type":4013,"columns":4265},[4266],{"items":4267},[4268,4273,4279,4284,4289,4294,4299,4304,4309],{"text":4269,"config":4270},"About",{"href":4271,"dataGaName":4272,"dataGaLocation":3991},"/company/","about",{"text":4274,"config":4275,"footerGa":4278},"Jobs",{"href":4276,"dataGaName":4277,"dataGaLocation":3991},"/jobs/","jobs",{"dataGaName":4277},{"text":4280,"config":4281},"Press",{"href":4282,"dataGaName":4283,"dataGaLocation":3991},"/press/","press",{"text":4285,"config":4286},"Handbook",{"href":4287,"dataGaName":4288,"dataGaLocation":3991},"https://handbook.gitlab.com/","handbook",{"text":4290,"config":4291},"Leadership",{"href":4292,"dataGaName":4293,"dataGaLocation":3991},"/company/team/e-group/","leadership",{"text":4295,"config":4296},"Investor relations",{"href":4297,"dataGaName":4298,"dataGaLocation":3991},"https://ir.gitlab.com/overview/default.aspx","investor relations",{"text":4300,"config":4301},"Trust Center",{"href":4302,"dataGaName":4303,"dataGaLocation":3991},"/security/","trust center",{"text":4305,"config":4306},"AI Transparency Center",{"href":4307,"dataGaName":4308,"dataGaLocation":3991},"/ai-transparency-center/","ai transparency center",{"text":4310,"config":4311},"Newsletter",{"href":4312,"dataGaName":4313,"dataGaLocation":3991},"/company/contact/#contact-forms","newsletter",{"text":4315,"config":4316,"menu":4317},"Contact us",{"dataNavLevelOne":4263},{"type":4013,"columns":4318},[4319],{"items":4320},[4321,4326,4331],{"text":4322,"config":4323},"Talk to sales",{"href":4324,"dataGaName":4325,"dataGaLocation":3991},"/sales/","talk to sales",{"text":4327,"config":4328},"Support portal",{"href":4329,"dataGaName":4330,"dataGaLocation":3991},"https://support.gitlab.com/hc/en-us","support portal",{"text":4332,"config":4333},"Customer portal",{"href":4334,"dataGaName":4335,"dataGaLocation":3991},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":4337,"login":4338,"suggestions":4345},"Close",{"text":4339,"link":4340},"To search repositories and projects, login to",{"text":4341,"config":4342},"gitlab.com",{"href":4005,"dataGaName":4343,"dataGaLocation":4344},"search login","search",{"text":4346,"default":4347},"Suggestions",[4348,4351,4354,4356,4360,4364],{"text":4349,"config":4350},"GitLab Duo Agent Platform",{"href":4069,"dataGaName":4349,"dataGaLocation":4344},{"text":4352,"config":4353},"Code Suggestions (AI)",{"href":4069,"dataGaName":4352,"dataGaLocation":4344},{"text":4022,"config":4355},{"href":27,"dataGaName":4022,"dataGaLocation":4344},{"text":4357,"config":4358},"GitLab on AWS",{"href":4359,"dataGaName":4357,"dataGaLocation":4344},"/partners/technology-partners/aws/",{"text":4361,"config":4362},"GitLab on Google Cloud",{"href":4363,"dataGaName":4361,"dataGaLocation":4344},"/partners/technology-partners/google-cloud-platform/",{"text":4365,"config":4366},"Why GitLab?",{"href":4090,"dataGaName":4365,"dataGaLocation":4344},{"freeTrial":4368,"mobileIcon":4373,"desktopIcon":4378,"secondaryButton":4381},{"text":4369,"config":4370},"Start free trial",{"href":4371,"dataGaName":3996,"dataGaLocation":4372},"https://gitlab.com/-/trials/new/","nav",{"altText":4374,"config":4375},"Gitlab Icon",{"src":4376,"dataGaName":4377,"dataGaLocation":4372},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203874/jypbw1jx72aexsoohd7x.svg","gitlab icon",{"altText":4374,"config":4379},{"src":4380,"dataGaName":4377,"dataGaLocation":4372},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203875/gs4c8p8opsgvflgkswz9.svg",{"text":4382,"config":4383},"Get Started",{"href":4384,"dataGaName":4385,"dataGaLocation":4372},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/get-started/","get started",{"freeTrial":4387,"mobileIcon":4391,"desktopIcon":4393},{"text":4388,"config":4389},"Learn more about GitLab Duo",{"href":4069,"dataGaName":4390,"dataGaLocation":4372},"gitlab duo",{"altText":4374,"config":4392},{"src":4376,"dataGaName":4377,"dataGaLocation":4372},{"altText":4374,"config":4394},{"src":4380,"dataGaName":4377,"dataGaLocation":4372},{"button":4396,"mobileIcon":4401,"desktopIcon":4403},{"text":4397,"config":4398},"/switch",{"href":4399,"dataGaName":4400,"dataGaLocation":4372},"#contact","switch",{"altText":4374,"config":4402},{"src":4376,"dataGaName":4377,"dataGaLocation":4372},{"altText":4374,"config":4404},{"src":4405,"dataGaName":4377,"dataGaLocation":4372},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1773335277/ohhpiuoxoldryzrnhfrh.png",{"freeTrial":4407,"mobileIcon":4412,"desktopIcon":4414},{"text":4408,"config":4409},"Back to pricing",{"href":4183,"dataGaName":4410,"dataGaLocation":4372,"icon":4411},"back to pricing","GoBack",{"altText":4374,"config":4413},{"src":4376,"dataGaName":4377,"dataGaLocation":4372},{"altText":4374,"config":4415},{"src":4380,"dataGaName":4377,"dataGaLocation":4372},{"title":4417,"button":4418,"config":4424},"Ship at agent speed. Prove every step. Transcend is here.",{"text":4419,"config":4420},"Watch now",{"href":4421,"dataGaName":4422,"dataGaLocation":4423},"/events/transcend/virtual/#watch-gitlab-transcend","transcend-october-2026-banner","global-banner",{"layout":4425,"disabled":3960},"release",{"data":4427},{"text":4428,"source":4429,"edit":4435,"contribute":4440,"config":4445,"items":4450,"minimal":4676},"Git is a trademark of Software Freedom Conservancy and our use of 'GitLab' is under license",{"text":4430,"config":4431},"View page source",{"href":4432,"dataGaName":4433,"dataGaLocation":4434},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":4436,"config":4437},"Edit this page",{"href":4438,"dataGaName":4439,"dataGaLocation":4434},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":4441,"config":4442},"Please contribute",{"href":4443,"dataGaName":4444,"dataGaLocation":4434},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":4446,"facebook":4447,"youtube":4448,"linkedin":4449},"https://twitter.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[4451,4498,4556,4603,4644],{"title":4181,"links":4452,"subMenu":4467},[4453,4457,4462],{"text":4454,"config":4455},"View plans",{"href":4183,"dataGaName":4456,"dataGaLocation":4434},"view plans",{"text":4458,"config":4459},"Why Premium?",{"href":4460,"dataGaName":4461,"dataGaLocation":4434},"/pricing/premium/","why premium",{"text":4463,"config":4464},"Why Ultimate?",{"href":4465,"dataGaName":4466,"dataGaLocation":4434},"/pricing/ultimate/","why ultimate",[4468],{"title":4469,"links":4470},"Contact Us",[4471,4474,4476,4478,4483,4488,4493],{"text":4472,"config":4473},"Contact sales",{"href":4324,"dataGaName":4001,"dataGaLocation":4434},{"text":4327,"config":4475},{"href":4329,"dataGaName":4330,"dataGaLocation":4434},{"text":4332,"config":4477},{"href":4334,"dataGaName":4335,"dataGaLocation":4434},{"text":4479,"config":4480},"Status",{"href":4481,"dataGaName":4482,"dataGaLocation":4434},"https://status.gitlab.com/","status",{"text":4484,"config":4485},"Terms of use",{"href":4486,"dataGaName":4487,"dataGaLocation":4434},"/terms/","terms of use",{"text":4489,"config":4490},"Privacy statement",{"href":4491,"dataGaName":4492,"dataGaLocation":4434},"/privacy/","privacy statement",{"text":4494,"config":4495},"Cookie preferences",{"dataGaName":4496,"dataGaLocation":4434,"id":4497,"isOneTrustButton":278},"cookie preferences","ot-sdk-btn",{"title":4009,"links":4499,"subMenu":4513},[4500,4503,4505,4507,4509,4511],{"text":4501,"config":4502},"Explore the platform",{"href":3919,"dataGaName":4011,"dataGaLocation":4434},{"text":4067,"config":4504},{"href":4069,"dataGaName":4070,"dataGaLocation":4434},{"text":4022,"config":4506},{"href":27,"dataGaName":4024,"dataGaLocation":4434},{"text":4072,"config":4508},{"href":4074,"dataGaName":4075,"dataGaLocation":4434},{"text":4054,"config":4510},{"href":4056,"dataGaName":4057,"dataGaLocation":4434},{"text":4085,"config":4512},{"href":4090,"dataGaName":4091,"dataGaLocation":4434},[4514],{"title":4515,"links":4516},"Topics",[4517,4521,4526,4531,4536,4541,4546,4551],{"text":4022,"config":4518},{"href":4519,"dataGaName":4520,"dataGaLocation":4434},"/topics/ci-cd/","cicd",{"text":4522,"config":4523},"GitOps",{"href":4524,"dataGaName":4525,"dataGaLocation":4434},"/topics/gitops/","gitops",{"text":4527,"config":4528},"DevOps",{"href":4529,"dataGaName":4530,"dataGaLocation":4434},"/topics/devops/","devops",{"text":4532,"config":4533},"Version Control",{"href":4534,"dataGaName":4535,"dataGaLocation":4434},"/topics/version-control/","version control",{"text":4537,"config":4538},"DevSecOps",{"href":4539,"dataGaName":4540,"dataGaLocation":4434},"/topics/devsecops/","devsecops",{"text":4542,"config":4543},"Cloud Native",{"href":4544,"dataGaName":4545,"dataGaLocation":4434},"/topics/cloud-native/","cloud native",{"text":4547,"config":4548},"AI for Coding",{"href":4549,"dataGaName":4550,"dataGaLocation":4434},"/topics/devops/ai-for-coding/","ai for coding",{"text":4552,"config":4553},"Agentic AI",{"href":4554,"dataGaName":4555,"dataGaLocation":4434},"/topics/agentic-ai/","agentic ai",{"title":4093,"links":4557},[4558,4561,4564,4567,4570,4573,4575,4577,4579,4581,4583,4586,4588,4591,4595,4599],{"text":4559,"config":4560},"DevOps modernization",{"href":4108,"dataGaName":4109,"dataGaLocation":4434},{"text":4562,"config":4563},"Security modernization",{"href":4113,"dataGaName":4114,"dataGaLocation":4434},{"text":4565,"config":4566},"AI modernization",{"href":4118,"dataGaName":4119,"dataGaLocation":4434},{"text":4568,"config":4569},"Financial services",{"href":4144,"dataGaName":4145,"dataGaLocation":4434},{"text":4571,"config":4572},"Public sector",{"href":4149,"dataGaName":4150,"dataGaLocation":4434},{"text":4152,"config":4574},{"href":4154,"dataGaName":4155,"dataGaLocation":4434},{"text":4157,"config":4576},{"href":4159,"dataGaName":4160,"dataGaLocation":4434},{"text":4162,"config":4578},{"href":4164,"dataGaName":4165,"dataGaLocation":4434},{"text":4167,"config":4580},{"href":4169,"dataGaName":4170,"dataGaLocation":4434},{"text":4124,"config":4582},{"href":4126,"dataGaName":4127,"dataGaLocation":4434},{"text":4584,"config":4585},"Small business",{"href":4131,"dataGaName":4132,"dataGaLocation":4434},{"text":4134,"config":4587},{"href":4136,"dataGaName":4137,"dataGaLocation":4434},{"text":4522,"config":4589},{"href":4590,"dataGaName":4525,"dataGaLocation":4434},"/solutions/gitops/",{"text":4592,"config":4593},"Software composition analysis",{"href":4061,"dataGaName":4594,"dataGaLocation":4434},"software composition analysis",{"text":4596,"config":4597},"Value stream management",{"href":4079,"dataGaName":4598,"dataGaLocation":4434},"value stream management",{"text":4600,"config":4601},"All solutions",{"href":4100,"dataGaName":4602,"dataGaLocation":4434},"all solutions",{"title":4186,"links":4604},[4605,4610,4615,4617,4619,4621,4623,4625,4627,4629,4631,4633,4635,4637,4640],{"text":4606,"config":4607},"Install",{"href":4608,"dataGaName":4609,"dataGaLocation":4434},"/install/","install",{"text":4611,"config":4612},"Quick start guides",{"href":4613,"dataGaName":4614,"dataGaLocation":4434},"/get-started/","quick setup checklists",{"text":4199,"config":4616},{"href":4201,"dataGaName":4202,"dataGaLocation":4434},{"text":4204,"config":4618},{"href":4206,"dataGaName":4207,"dataGaLocation":4434},{"text":4209,"config":4620},{"href":4211,"dataGaName":4212,"dataGaLocation":4434},{"text":4214,"config":4622},{"href":4216,"dataGaName":4217,"dataGaLocation":4434},{"text":4219,"config":4624},{"href":4221,"dataGaName":4222,"dataGaLocation":4434},{"text":4227,"config":4626},{"href":4229,"dataGaName":4230,"dataGaLocation":4434},{"text":4232,"config":4628},{"href":4234,"dataGaName":4235,"dataGaLocation":4434},{"text":4237,"config":4630},{"href":4239,"dataGaName":4240,"dataGaLocation":4434},{"text":4242,"config":4632},{"href":4244,"dataGaName":4245,"dataGaLocation":4434},{"text":4247,"config":4634},{"href":4249,"dataGaName":4250,"dataGaLocation":4434},{"text":4310,"config":4636},{"href":4312,"dataGaName":4313,"dataGaLocation":4434},{"text":4638,"config":4639},"What's new",{"href":4258,"dataGaName":4259,"dataGaLocation":4434},{"text":4641,"config":4642},"All resources",{"href":4193,"dataGaName":4643,"dataGaLocation":4434},"all resources",{"title":4261,"links":4645},[4646,4648,4650,4652,4654,4656,4658,4660,4662,4666,4671],{"text":4269,"config":4647},{"href":4271,"dataGaName":4263,"dataGaLocation":4434},{"text":4274,"config":4649},{"href":4276,"dataGaName":4277,"dataGaLocation":4434},{"text":4280,"config":4651},{"href":4282,"dataGaName":4283,"dataGaLocation":4434},{"text":4285,"config":4653},{"href":4287,"dataGaName":4288,"dataGaLocation":4434},{"text":4290,"config":4655},{"href":4292,"dataGaName":4293,"dataGaLocation":4434},{"text":4295,"config":4657},{"href":4297,"dataGaName":4298,"dataGaLocation":4434},{"text":4300,"config":4659},{"href":4302,"dataGaName":4303,"dataGaLocation":4434},{"text":4305,"config":4661},{"href":4307,"dataGaName":4308,"dataGaLocation":4434},{"text":4663,"config":4664},"Sustainability",{"href":4665,"dataGaName":4663,"dataGaLocation":4434},"/sustainability/",{"text":4667,"config":4668},"Diversity, inclusion and belonging (DIB)",{"href":4669,"dataGaName":4670,"dataGaLocation":4434},"/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":4672,"config":4673},"Modern Slavery Transparency Statement",{"href":4674,"dataGaName":4675,"dataGaLocation":4434},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"items":4677},[4678,4681,4684],{"text":4679,"config":4680},"Terms",{"href":4486,"dataGaName":4487,"dataGaLocation":4434},{"text":4682,"config":4683},"Cookies",{"dataGaName":4496,"dataGaLocation":4434,"id":4497,"isOneTrustButton":278},{"text":4685,"config":4686},"Privacy",{"href":4491,"dataGaName":4492,"dataGaLocation":4434},[4688],{"id":4689,"title":7,"body":3959,"config":4690,"content":4692,"description":3959,"extension":4696,"meta":4697,"navigation":278,"path":4698,"seo":4699,"stem":4700,"__hash__":4701},"blogAuthors/en-us/blog/authors/connor-shea.yml",{"template":4691},"BlogAuthor",{"name":7,"config":4693},{"headshot":4694,"ctfId":4695},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749659488/Blog/Author%20Headshots/gitlab-logo-extra-whitespace.png","Connor-Shea","yml",{},"/en-us/blog/authors/connor-shea",{},"en-us/blog/authors/connor-shea","lnDTMTsyhS-xSqiXAW97VhBYRYVcmRSgO10ag12irLM",[4703,4711,4719],{"title":4704,"description":4705,"heroImage":4706,"category":3955,"date":4707,"authors":4708,"slug":4710,"externalUrl":3959},"Two front doors: Module-level access in a Django GRC app","A technical deep-dive on restructuring a Django internal tool to support two distinct GitLab teams: Security Compliance and Internal Audit.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097663/Blog/Hero%20Images/Blog/Hero%20Images/Workflow%201800x945_2gQoQIbY9NvjLFpXtsxtXy_1750097663612.png","2026-10-05",[4709],"Donovan Felton","module-level-access-in-a-django-grc-app",{"title":4712,"description":4713,"heroImage":4714,"category":3955,"date":4715,"authors":4716,"slug":4718,"externalUrl":3959},"How GitLab reduced code-per-agentic-flow ratio by 45%","Learn how Flow Registry, a component-based framework that compiles YAML configurations into fully functional LangGraph flows, helped improve scalability and more.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1774465167/n5hlvrsrheadeccyr1oz.png","2026-09-22",[4717],"Mikolaj Wawrzyniak","how-gitlab-reduced-code-per-agentic-flow-ratio",{"title":4720,"description":4721,"heroImage":4722,"category":3955,"date":4723,"authors":4724,"slug":4726,"externalUrl":3959},"Co-Create: Building GitLab with our users","How GitLab users collaborated with us to build practical product improvements in the first half of 2026.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099203/Blog/Hero%20Images/Blog/Hero%20Images/blog-image-template-1800x945%20%2820%29_2bJGC5ZP3WheoqzlLT05C5_1750099203484.png","2026-09-10",[4725],"Isa Huerga","co-create-h1-2026",{"header":4728,"text":4729,"button":4733,"disclaimer":4736},"Start your free\n\n30-day GitLab trial\n",[4730,4731,4732],"GitLab Duo Agent Platform access","Try our most advanced features","Automate complex tasks with AI",{"text":3993,"config":4734},{"href":4735,"dataGaName":3996,"dataGaLocation":4230},"https://gitlab.com/-/trials/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/blog/","No credit card required.",{"header":4738,"blurb":4739,"button":4740,"secondaryButton":4745},"Start building faster today","See what your team can do with the intelligent orchestration platform for DevSecOps.\n",{"text":4741,"config":4742},"Get your free trial",{"href":4743,"dataGaName":3996,"dataGaLocation":4744},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/","feature",{"text":4472,"config":4746},{"href":4324,"dataGaName":4001,"dataGaLocation":4744},1791990504532]