[{"data":1,"prerenderedAt":5787},["ShallowReactive",2],{"/blog/set-up-infrastructure-for-cloud-development-environments":3,"navigation-en-us":5007,"banner-en-us":5430,"footer-en-us":5440,"blog-post-authors-en-us-Michael Friedrich":5682,"blog-related-posts-en-us-set-up-infrastructure-for-cloud-development-environments":5697,"blog-promotions-en-us":5723,"next-steps-en-us":5777},{"id":4,"title":5,"authors":6,"body":8,"category":4983,"date":4984,"description":4985,"extension":4986,"externalUrl":4987,"faq":4987,"featured":4988,"heroImage":4989,"meta":4990,"navigation":485,"path":4991,"seo":4992,"slug":4997,"stem":4998,"tags":4999,"template":5005,"updatedDate":4987,"__hash__":5006},"blogPosts/en-us/blog/set-up-infrastructure-for-cloud-development-environments.md","Set up your infrastructure for on-demand, cloud-based development environments in GitLab",[7],"Michael Friedrich",{"type":9,"value":10,"toc":4955},"minimark",[11,15,18,21,160,164,172,190,199,220,231,234,237,258,262,265,268,287,293,300,303,358,377,380,388,560,563,571,578,642,645,651,654,746,752,756,762,771,778,804,810,813,820,905,908,917,933,936,1125,1128,1144,1193,1199,1451,1498,1565,1573,1605,1608,1635,1739,1782,1793,1809,1815,1852,1855,1863,1888,1909,2044,2053,2139,2145,2190,2193,2202,2259,2267,2276,2316,2322,2352,2355,2372,2375,2380,2388,2411,2424,2662,2669,2737,2740,2769,2975,2994,3083,3089,3171,3176,3179,3198,3210,3368,3371,3378,3384,3387,3534,3789,3796,3802,3818,3824,3843,3869,3872,3892,3898,3907,3913,3926,3937,3955,3958,3964,3973,3976,3994,4019,4027,4033,4054,4061,4096,4108,4133,4148,4164,4172,4187,4195,4215,4222,4225,4228,4276,4279,4288,4297,4300,4303,4308,4311,4329,4332,4355,4362,4399,4406,4455,4459,4467,4513,4524,4692,4698,4702,4705,4717,4730,4735,4773,4780,4809,4816,4849,4855,4885,4888,4902,4905,4908,4922,4937,4951],[12,13,14],"p",{},"Cloud-based development environments enable a better developer onboarding experience and help make teams more efficient. In this tutorial, you'll learn how to ready your infrastructure for on-demand, cloud-based development environments. You'll also learn how to set up the requirements, manage Kubernetes clusters in different clouds, create your first workspaces and custom images, and get tips for troubleshooting.",[12,16,17],{},"The GitLab agent for Kubernetes, an OAuth GitLab app, and a proxy pod deployment make the setup reproducible in different Kubernetes cluster environments and follow cloud-native best practices. Bringing your infrastructure allows platform teams to store the workspace data securely, control resource usage, harden security, and troubleshoot the deployments in known ways.",[12,19,20],{},"This blog post is a long read so feel free to navigate to the sections of interest. However, if you want to follow the tutorial step by step, the sections depend on one another for the parts pertaining to infrastructure setup.",[22,23,24,32,52,58,96,102,108,128,154],"ul",{},[25,26,27],"li",{},[28,29,31],"a",{"href":30},"#development-environments-on-your-infrastructure","Development environments on your infrastructure",[25,33,34,38],{},[28,35,37],{"href":36},"#requirements","Requirements",[22,39,40,46],{},[25,41,42],{},[28,43,45],{"href":44},"#workspaces-domain","Workspaces domain",[25,47,48],{},[28,49,51],{"href":50},"#tls-certificates","TLS certificates",[25,53,54],{},[28,55,57],{"href":56},"#gitlab-oauth-application","GitLab OAuth application ",[25,59,60,64],{},[28,61,63],{"href":62},"#kubernetes-cluster-setup","Kubernetes cluster setup",[22,65,66,72,78,84,90],{},[25,67,68],{},[28,69,71],{"href":70},"#set-up-infrastructure-with-google-kubernetes-engine=gke","Set up infrastructure with Google Kubernetes Engine (GKE)",[25,73,74],{},[28,75,77],{"href":76},"#set-up-infrastructure-with-amazon-elastic-kubernetes-service-eks","Set up infrastructure with Amazon Elastic Kubernetes Service (EKS)",[25,79,80],{},[28,81,83],{"href":82},"#set-up-infrastructure-with-azure-managed-kubernetes-service-aks","Set up infrastructure with Azure Managed Kubernetes Service (AKS)",[25,85,86],{},[28,87,89],{"href":88},"#set-up-infrastructure-with-civo-cloud-kubernetes","Set up infrastructure with Civo Cloud Kubernetes",[25,91,92],{},[28,93,95],{"href":94},"#set-up-infrastructure-with-self-managed-kubernetes","Set up infrastructure with self-managed Kubernetes",[25,97,98],{},[28,99,101],{"href":100},"#workspaces-proxy-installation-into-kubernetes","Workspaces proxy installation into Kubernetes",[25,103,104],{},[28,105,107],{"href":106},"#agent-for-kubernetes-installation","Agent for Kubernetes installation",[25,109,110,114],{},[28,111,113],{"href":112},"#workspaces-creation","Workspaces creation",[22,115,116,122],{},[25,117,118],{},[28,119,121],{"href":120},"#create-the-first-workspaces","Create the first workspaces",[25,123,124],{},[28,125,127],{"href":126},"#custom-workspace-container-images","Custom workspace container images",[25,129,130,134],{},[28,131,133],{"href":132},"#tips","Tips",[22,135,136,142,148],{},[25,137,138],{},[28,139,141],{"href":140},"#certificate-management","Certificate management",[25,143,144],{},[28,145,147],{"href":146},"#troubleshooting","Troubleshooting",[25,149,150],{},[28,151,153],{"href":152},"#contribute","Contribute",[25,155,156],{},[28,157,159],{"href":158},"#share-your-feedback","Share your feedback",[161,162,31],"h2",{"id":163},"development-environments-on-your-infrastructure",[12,165,166,167,171],{},"Secure, on-demand, cloud-based development workspaces are ",[28,168,170],{"href":169},"/blog/introducing-workspaces-beta/","available in beta for public projects"," for Premium and Ultimate customers. The first iteration allows you to bring your own infrastructure as a Kubernetes cluster. GitLab already deeply integrates with Kubernetes through the GitLab agent for Kubernetes, setting the foundation for configuration and cluster management.",[12,173,174,175,180,181,185,186,189],{},"Users can define and use a development environment template in a project. Workspaces in GitLab support the ",[28,176,179],{"href":177,"rel":178},"https://docs.gitlab.com/user/workspace/#devfile",[],"devfile specification"," as ",[182,183,184],"code",{},".devfile.yaml"," in the project repository root. The devfile attributes allow configuring of the workspace. For example, the ",[182,187,188],{},"image"," attribute specifies the container image to run and create the workspace in isolated container environments. The containers require a cluster orchestrator, such as Kubernetes, that manages resource usage and ensures data security and safety. Workspaces also need authorization: Project source code may contain sensitive intellectual property or otherwise confidential data in specific environments. The setup requires a GitLab OAuth application as the foundation here.",[12,191,192,193,198],{},"The following steps provide an in-depth setup guide for different cloud providers. If you prefer to set up your own environment, please follow the ",[28,194,197],{"href":195,"rel":196},"https://docs.gitlab.com/user/workspace/#prerequisites",[],"documentation for workspace prerequisites",". In general, we will practice the following steps:\n0. (Optional) Register a workspaces domain, and create TLS certificates.",[200,201,202,205,208,211,214,217],"ol",{},[25,203,204],{},"Create a Kubernetes cluster and configure access and requirements.",[25,206,207],{},"Install an Ingress controller.",[25,209,210],{},"Set up the workspaces proxy with the domain, TLS certificates, and OAuth app.",[25,212,213],{},"Create a new GitLab group with a GitLab agent project. The agent can be used for all projects in that group.",[25,215,216],{},"Install the GitLab agent for Kubernetes using the UI provided Helm chart command.",[25,218,219],{},"Create an example project with a devfile configuration for workspaces.",[12,221,222,223,226,227,230],{},"Some commands do not use the terminal indicator (",[182,224,225],{},"$"," or ",[182,228,229],{},"#",") to support easier copy-paste of command blocks into terminals.",[161,232,37],{"id":233},"requirements",[12,235,236],{},"The steps in this blog post require the following CLI tools:",[200,238,239,249,255],{},[25,240,241,244,245,248],{},[182,242,243],{},"kubectl"," and ",[182,246,247],{},"helm"," for Kubernetes",[25,250,251,254],{},[182,252,253],{},"certbot"," for Let's Encrypt",[25,256,257],{},"git, curl, dig, openssl, and sslscan for troubleshooting",[259,260,45],"h3",{"id":261},"workspaces-domain",[12,263,264],{},"Workspaces require a domain with DNS entries. Cloud providers, for example, Google Cloud, also provide domain services which integrate more easily. You can also register and manage domains with your preferred provider.",[12,266,267],{},"The required DNS entries will be:",[22,269,270,284],{},[25,271,272,273,276,277,280,281],{},"Wildcard DNS (",[182,274,275],{},"*.remote-dev.dev",") and hostname (",[182,278,279],{},"remote-dev.dev",") A/AAAA records pointing to the external Kubernetes external IP: ",[182,282,283],{},"kubectl get services -A",[25,285,286],{},"(Optional, with Let's Encrypt) ACME DNS challenge entries as TXT records",[12,288,289,290,292],{},"After acquiring a domain, wait until the Kubernetes setup is ready and extract the A/AAAA records for the DNS settings. The following example shows how ",[182,291,279],{}," is configured in the Google Cloud DNS service.",[12,294,295],{},[296,297],"img",{"alt":298,"src":299},"GitLab remote development workspaces, example DNS configuration for remote-dev.dev","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398574/blog/Content%20Images/infrastructure-cloud-development-environments/gitlab_remote_dev_workspaces_google_cloud_dns_remote-dev.dev-entries.png",[12,301,302],{},"Export shell variables that define the workspaces domains, and the email contact. These variables will be used in all setup steps below.",[304,305,310],"pre",{"className":306,"code":307,"language":308,"meta":309,"style":309},"language-shell shiki shiki-themes github-light","export EMAIL=\"user@company.com\"\nexport GITLAB_WORKSPACES_PROXY_DOMAIN=\"remote-dev.dev\"\nexport GITLAB_WORKSPACES_WILDCARD_DOMAIN=\"*.remote-dev.dev\"\n","shell","",[182,311,312,332,345],{"__ignoreMap":309},[313,314,317,321,325,328],"span",{"class":315,"line":316},"line",1,[313,318,320],{"class":319},"sD7c4","export",[313,322,324],{"class":323},"sgsFI"," EMAIL",[313,326,327],{"class":319},"=",[313,329,331],{"class":330},"sYBdl","\"user@company.com\"\n",[313,333,335,337,340,342],{"class":315,"line":334},2,[313,336,320],{"class":319},[313,338,339],{"class":323}," GITLAB_WORKSPACES_PROXY_DOMAIN",[313,341,327],{"class":319},[313,343,344],{"class":330},"\"remote-dev.dev\"\n",[313,346,348,350,353,355],{"class":315,"line":347},3,[313,349,320],{"class":319},[313,351,352],{"class":323}," GITLAB_WORKSPACES_WILDCARD_DOMAIN",[313,354,327],{"class":319},[313,356,357],{"class":330},"\"*.remote-dev.dev\"\n",[12,359,360,364,365,367,368,370,371,376],{},[361,362,363],"strong",{},"Note:"," This blog post will show the example domain ",[182,366,279],{}," for better understanding with a working example. The domain ",[182,369,279],{}," is maintained by the ",[28,372,375],{"href":373,"rel":374},"https://handbook.gitlab.com/handbook/marketing/developer-relations/developer-advocacy/projects/",[],"Developer Evangelism team at GitLab",". There are no public demo environments available at the time of writing this blog post.",[259,378,51],{"id":379},"tls-certificates",[12,381,382,383,387],{},"TLS certificates can be managed with different methods. To get started quickly, it is recommended to follow the ",[28,384,386],{"href":195,"rel":385},[],"documentation steps"," with Let's Encrypt and later consider production requirements with TLS certificates.",[304,389,391],{"className":306,"code":390,"language":308,"meta":309,"style":309},"certbot -d \"${GITLAB_WORKSPACES_PROXY_DOMAIN}\" \\\n  -m \"${EMAIL}\" \\\n  --config-dir ~/.certbot/config \\\n  --logs-dir ~/.certbot/logs \\\n  --work-dir ~/.certbot/work \\\n  --manual \\\n  --preferred-challenges dns certonly\n\n  certbot -d \"${GITLAB_WORKSPACES_WILDCARD_DOMAIN}\" \\\n  -m \"${EMAIL}\" \\\n  --config-dir ~/.certbot/config \\\n  --logs-dir ~/.certbot/logs \\\n  --work-dir ~/.certbot/work \\\n  --manual \\\n  --preferred-challenges dns certonly\n",[182,392,393,414,428,438,449,460,468,480,487,504,517,526,535,544,551],{"__ignoreMap":309},[313,394,395,398,402,405,408,411],{"class":315,"line":316},[313,396,253],{"class":397},"s7eDp",[313,399,401],{"class":400},"sYu0t"," -d",[313,403,404],{"class":330}," \"${",[313,406,407],{"class":323},"GITLAB_WORKSPACES_PROXY_DOMAIN",[313,409,410],{"class":330},"}\"",[313,412,413],{"class":400}," \\\n",[313,415,416,419,421,424,426],{"class":315,"line":334},[313,417,418],{"class":400},"  -m",[313,420,404],{"class":330},[313,422,423],{"class":323},"EMAIL",[313,425,410],{"class":330},[313,427,413],{"class":400},[313,429,430,433,436],{"class":315,"line":347},[313,431,432],{"class":400},"  --config-dir",[313,434,435],{"class":330}," ~/.certbot/config",[313,437,413],{"class":400},[313,439,441,444,447],{"class":315,"line":440},4,[313,442,443],{"class":400},"  --logs-dir",[313,445,446],{"class":330}," ~/.certbot/logs",[313,448,413],{"class":400},[313,450,452,455,458],{"class":315,"line":451},5,[313,453,454],{"class":400},"  --work-dir",[313,456,457],{"class":330}," ~/.certbot/work",[313,459,413],{"class":400},[313,461,463,466],{"class":315,"line":462},6,[313,464,465],{"class":400},"  --manual",[313,467,413],{"class":400},[313,469,471,474,477],{"class":315,"line":470},7,[313,472,473],{"class":400},"  --preferred-challenges",[313,475,476],{"class":330}," dns",[313,478,479],{"class":330}," certonly\n",[313,481,483],{"class":315,"line":482},8,[313,484,486],{"emptyLinePlaceholder":485},true,"\n",[313,488,490,493,495,497,500,502],{"class":315,"line":489},9,[313,491,492],{"class":397},"  certbot",[313,494,401],{"class":400},[313,496,404],{"class":330},[313,498,499],{"class":323},"GITLAB_WORKSPACES_WILDCARD_DOMAIN",[313,501,410],{"class":330},[313,503,413],{"class":400},[313,505,507,509,511,513,515],{"class":315,"line":506},10,[313,508,418],{"class":400},[313,510,404],{"class":330},[313,512,423],{"class":323},[313,514,410],{"class":330},[313,516,413],{"class":400},[313,518,520,522,524],{"class":315,"line":519},11,[313,521,432],{"class":400},[313,523,435],{"class":330},[313,525,413],{"class":400},[313,527,529,531,533],{"class":315,"line":528},12,[313,530,443],{"class":400},[313,532,446],{"class":330},[313,534,413],{"class":400},[313,536,538,540,542],{"class":315,"line":537},13,[313,539,454],{"class":400},[313,541,457],{"class":330},[313,543,413],{"class":400},[313,545,547,549],{"class":315,"line":546},14,[313,548,465],{"class":400},[313,550,413],{"class":400},[313,552,554,556,558],{"class":315,"line":553},15,[313,555,473],{"class":400},[313,557,476],{"class":330},[313,559,479],{"class":330},[12,561,562],{},"The Let's Encrypt CLI prompts you for the ACME DNS challenge. This requires setting TXT records for the challenge session immediately. Add the DNS records and specify a low TTL (time-to-live) of 300 seconds to update the records during the first steps.",[304,564,569],{"className":565,"code":567,"language":568,"meta":309},[566],"language-text","_acme-challenge TXT \u003Cstringfromletsencryptacmechallenge>\n","text",[182,570,567],{"__ignoreMap":309},[12,572,573,574,577],{},"You can verify the DNS records using the ",[182,575,576],{},"dig"," CLI command.",[304,579,581],{"className":306,"code":580,"language":308,"meta":309,"style":309},"$ dig _acme-challenge.remote-dev.dev txt\n...\n;; ANSWER SECTION:\n_acme-challenge.remote-dev.dev. 246 IN  TXT \"TlGRM9JGdXHGVklPWgytflxWDF82Sv04nF--Wl9JFvg\"\n_acme-challenge.remote-dev.dev. 246 IN  TXT \"CqG_54w6I0heWF3wLMAmUAitPcUMs9qAU9b8QhBWFj8\"\n",[182,582,583,596,601,612,629],{"__ignoreMap":309},[313,584,585,587,590,593],{"class":315,"line":316},[313,586,225],{"class":397},[313,588,589],{"class":330}," dig",[313,591,592],{"class":330}," _acme-challenge.remote-dev.dev",[313,594,595],{"class":330}," txt\n",[313,597,598],{"class":315,"line":334},[313,599,600],{"class":400},"...\n",[313,602,603,606,609],{"class":315,"line":347},[313,604,605],{"class":323},";; ",[313,607,608],{"class":397},"ANSWER",[313,610,611],{"class":330}," SECTION:\n",[313,613,614,617,620,623,626],{"class":315,"line":440},[313,615,616],{"class":397},"_acme-challenge.remote-dev.dev.",[313,618,619],{"class":400}," 246",[313,621,622],{"class":330}," IN",[313,624,625],{"class":330},"  TXT",[313,627,628],{"class":330}," \"TlGRM9JGdXHGVklPWgytflxWDF82Sv04nF--Wl9JFvg\"\n",[313,630,631,633,635,637,639],{"class":315,"line":451},[313,632,616],{"class":397},[313,634,619],{"class":400},[313,636,622],{"class":330},[313,638,625],{"class":330},[313,640,641],{"class":330}," \"CqG_54w6I0heWF3wLMAmUAitPcUMs9qAU9b8QhBWFj8\"\n",[12,643,644],{},"Once the Let's Encrypt routine is complete, note the TLS certificate location.",[304,646,649],{"className":647,"code":648,"language":568,"meta":309},[566],"Successfully received certificate.\nCertificate is saved at: /Users/mfriedrich/.certbot/config/live/remote-dev.dev/fullchain.pem\nKey is saved at:         /Users/mfriedrich/.certbot/config/live/remote-dev.dev/privkey.pem\nThis certificate expires on 2023-08-15.\nThese files will be updated when the certificate renews.\n\nSuccessfully received certificate.\nCertificate is saved at: /Users/mfriedrich/.certbot/config/live/remote-dev.dev-0001/fullchain.pem\nKey is saved at:         /Users/mfriedrich/.certbot/config/live/remote-dev.dev-0001/privkey.pem\nThis certificate expires on 2023-08-15.\nThese files will be updated when the certificate renews.\n",[182,650,648],{"__ignoreMap":309},[12,652,653],{},"Export the TLS certificate paths into environment variables for the following setup steps.",[304,655,657],{"className":306,"code":656,"language":308,"meta":309,"style":309},"export WORKSPACES_DOMAIN_CERT=\"${HOME}/.certbot/config/live/${GITLAB_WORKSPACES_PROXY_DOMAIN}/fullchain.pem\"\nexport WORKSPACES_DOMAIN_KEY=\"${HOME}/.certbot/config/live/${GITLAB_WORKSPACES_PROXY_DOMAIN}/privkey.pem\"\n\nexport WILDCARD_DOMAIN_CERT=\"${HOME}/.certbot/config/live/${GITLAB_WORKSPACES_PROXY_DOMAIN}-0001/fullchain.pem\"\nexport WILDCARD_DOMAIN_KEY=\"${HOME}/.certbot/config/live/${GITLAB_WORKSPACES_PROXY_DOMAIN}-0001/privkey.pem\"\n",[182,658,659,682,702,706,726],{"__ignoreMap":309},[313,660,661,663,666,668,671,674,677,679],{"class":315,"line":316},[313,662,320],{"class":319},[313,664,665],{"class":323}," WORKSPACES_DOMAIN_CERT",[313,667,327],{"class":319},[313,669,670],{"class":330},"\"${",[313,672,673],{"class":323},"HOME",[313,675,676],{"class":330},"}/.certbot/config/live/${",[313,678,407],{"class":323},[313,680,681],{"class":330},"}/fullchain.pem\"\n",[313,683,684,686,689,691,693,695,697,699],{"class":315,"line":334},[313,685,320],{"class":319},[313,687,688],{"class":323}," WORKSPACES_DOMAIN_KEY",[313,690,327],{"class":319},[313,692,670],{"class":330},[313,694,673],{"class":323},[313,696,676],{"class":330},[313,698,407],{"class":323},[313,700,701],{"class":330},"}/privkey.pem\"\n",[313,703,704],{"class":315,"line":347},[313,705,486],{"emptyLinePlaceholder":485},[313,707,708,710,713,715,717,719,721,723],{"class":315,"line":440},[313,709,320],{"class":319},[313,711,712],{"class":323}," WILDCARD_DOMAIN_CERT",[313,714,327],{"class":319},[313,716,670],{"class":330},[313,718,673],{"class":323},[313,720,676],{"class":330},[313,722,407],{"class":323},[313,724,725],{"class":330},"}-0001/fullchain.pem\"\n",[313,727,728,730,733,735,737,739,741,743],{"class":315,"line":451},[313,729,320],{"class":319},[313,731,732],{"class":323}," WILDCARD_DOMAIN_KEY",[313,734,327],{"class":319},[313,736,670],{"class":330},[313,738,673],{"class":323},[313,740,676],{"class":330},[313,742,407],{"class":323},[313,744,745],{"class":330},"}-0001/privkey.pem\"\n",[12,747,748,751],{},[361,749,750],{},"Note",": If you prefer to use your certificates, please copy the files into a safe location, and export the environment variables with the path details.",[161,753,755],{"id":754},"gitlab-oauth-application","GitLab OAuth application",[12,757,758],{},[759,760,761],"em",{},"After preparing the requirements, continue with the components setup.",[12,763,764,765,770],{},"Create a ",[28,766,769],{"href":767,"rel":768},"https://docs.gitlab.com/integration/oauth_provider/",[],"group-owned OAuth application"," for the remote development workspaces group. Creating a centrally managed app with a service account or group with limited access is recommended for production use.",[12,772,773,774,777],{},"Navigate into the group ",[182,775,776],{},"Settings > Applications"," and specify the following values:",[200,779,780,787,797],{},[25,781,782,783,786],{},"Name: ",[182,784,785],{},"Remote Development workspaces by \u003Cresponsible team> - \u003Cdomain>",". Add the reponsible team that is trusted in your organization. For debugging, add the domain. There might be multiple authorization groups, this helps the identification which workspace domain is used.",[25,788,789,790,793,794,796],{},"Redirect URI: ",[182,791,792],{},"https://\u003CGITLAB_WORKSPACES_PROXY_DOMAIN>/auth/callback",". Replace ",[182,795,407],{}," with the domain string value.",[25,798,799,800,803],{},"Set the scopes to ",[182,801,802],{},"api, read_user, openid, profile"," .",[12,805,806],{},[296,807],{"alt":808,"src":809},"GitLab remote development workspaces, OAuth application in the group settings","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398574/blog/Content%20Images/infrastructure-cloud-development-environments/gitlab_remote_dev_workspaces_oauth_app_create.png",[12,811,812],{},"Store the OAuth application details in your password vault, and export them as shell environment variables for the next setup steps.",[12,814,815,816,819],{},"Create a configuration secret for the proxy as a signing key (",[182,817,818],{},"SIGNING_KEY","), and store it in a safe place (for example, use a secrets vault like 1Password to create and store the key).",[304,821,823],{"className":306,"code":822,"language":308,"meta":309,"style":309},"export CLIENT_ID=\"XXXXXXXXX\" # Look into password vault and set\nexport CLIENT_SECRET=\"XXXXXXXXXX\" # Look into password vault and set\nexport REDIRECT_URI=\"https://${GITLAB_WORKSPACES_PROXY_DOMAIN}/auth/callback\"\n\nexport GITLAB_URL=\"https://gitlab.com\" # Replace with your self-managed GitLab instance URL if not using GitLab.com SaaS\nexport SIGNING_KEY=\"a_random_key_consisting_of_letters_numbers_and_special_chars\" # Look into password vault and set\n",[182,824,825,841,855,872,876,891],{"__ignoreMap":309},[313,826,827,829,832,834,837],{"class":315,"line":316},[313,828,320],{"class":319},[313,830,831],{"class":323}," CLIENT_ID",[313,833,327],{"class":319},[313,835,836],{"class":330},"\"XXXXXXXXX\"",[313,838,840],{"class":839},"sAwPA"," # Look into password vault and set\n",[313,842,843,845,848,850,853],{"class":315,"line":334},[313,844,320],{"class":319},[313,846,847],{"class":323}," CLIENT_SECRET",[313,849,327],{"class":319},[313,851,852],{"class":330},"\"XXXXXXXXXX\"",[313,854,840],{"class":839},[313,856,857,859,862,864,867,869],{"class":315,"line":347},[313,858,320],{"class":319},[313,860,861],{"class":323}," REDIRECT_URI",[313,863,327],{"class":319},[313,865,866],{"class":330},"\"https://${",[313,868,407],{"class":323},[313,870,871],{"class":330},"}/auth/callback\"\n",[313,873,874],{"class":315,"line":440},[313,875,486],{"emptyLinePlaceholder":485},[313,877,878,880,883,885,888],{"class":315,"line":451},[313,879,320],{"class":319},[313,881,882],{"class":323}," GITLAB_URL",[313,884,327],{"class":319},[313,886,887],{"class":330},"\"https://gitlab.com\"",[313,889,890],{"class":839}," # Replace with your self-managed GitLab instance URL if not using GitLab.com SaaS\n",[313,892,893,895,898,900,903],{"class":315,"line":462},[313,894,320],{"class":319},[313,896,897],{"class":323}," SIGNING_KEY",[313,899,327],{"class":319},[313,901,902],{"class":330},"\"a_random_key_consisting_of_letters_numbers_and_special_chars\"",[313,904,840],{"class":839},[161,906,63],{"id":907},"kubernetes-cluster-setup",[12,909,910,911,916],{},"The following sections describe how to set up a Kubernetes cluster in different cloud and on-premises environments and install an ",[28,912,915],{"href":913,"rel":914},"https://kubernetes.io/docs/concepts/services-networking/ingress-controllers/",[],"ingress controller"," for HTTP access. After completing the Kubernetes setup, you can continue with the workspaces proxy and agent setup steps.",[12,918,919,932],{},[361,920,921,922,925,926,931],{},"Choose one method to create a Kubernetes cluster. Note: Use ",[182,923,924],{},"amd64"," as platform architecture ",[28,927,930],{"href":928,"rel":929},"https://gitlab.com/groups/gitlab-org/-/epics/10594",[],"until multi-architecture support is available for running workspaces","."," Cloud environments with Arm support will not work yet, for example AWS EKS on Graviton EC2 instances.",[12,934,935],{},"You should have defined the following variables from the previous setup steps:",[304,937,941],{"className":938,"code":939,"language":940,"meta":309,"style":309},"language-sh shiki shiki-themes github-light","export EMAIL=\"user@company.com\"\nexport GITLAB_WORKSPACES_PROXY_DOMAIN=\"remote-dev.dev\"\nexport GITLAB_WORKSPACES_WILDCARD_DOMAIN=\"*.remote-dev.dev\"\n\nexport WORKSPACES_DOMAIN_CERT=\"${HOME}/.certbot/config/live/${GITLAB_WORKSPACES_PROXY_DOMAIN}/fullchain.pem\"\nexport WORKSPACES_DOMAIN_KEY=\"${HOME}/.certbot/config/live/${GITLAB_WORKSPACES_PROXY_DOMAIN}/privkey.pem\"\n\nexport WILDCARD_DOMAIN_CERT=\"${HOME}/.certbot/config/live/${GITLAB_WORKSPACES_PROXY_DOMAIN}-0001/fullchain.pem\"\nexport WILDCARD_DOMAIN_KEY=\"${HOME}/.certbot/config/live/${GITLAB_WORKSPACES_PROXY_DOMAIN}-0001/privkey.pem\"\n\nexport CLIENT_ID=\"XXXXXXXXX\" # Look into password vault and set\nexport CLIENT_SECRET=\"XXXXXXXXXX\" # Look into password vault and set\nexport REDIRECT_URI=\"https://${GITLAB_WORKSPACES_PROXY_DOMAIN}/auth/callback\"\n\nexport GITLAB_URL=\"https://gitlab.com\" # Replace with your self-managed GitLab instance URL if not using GitLab.com SaaS\nexport SIGNING_KEY=\"XXXXXXXX\" # Look into password vault and set\n\n","sh",[182,942,943,953,963,973,977,995,1013,1017,1035,1053,1057,1069,1081,1095,1099,1111],{"__ignoreMap":309},[313,944,945,947,949,951],{"class":315,"line":316},[313,946,320],{"class":319},[313,948,324],{"class":323},[313,950,327],{"class":319},[313,952,331],{"class":330},[313,954,955,957,959,961],{"class":315,"line":334},[313,956,320],{"class":319},[313,958,339],{"class":323},[313,960,327],{"class":319},[313,962,344],{"class":330},[313,964,965,967,969,971],{"class":315,"line":347},[313,966,320],{"class":319},[313,968,352],{"class":323},[313,970,327],{"class":319},[313,972,357],{"class":330},[313,974,975],{"class":315,"line":440},[313,976,486],{"emptyLinePlaceholder":485},[313,978,979,981,983,985,987,989,991,993],{"class":315,"line":451},[313,980,320],{"class":319},[313,982,665],{"class":323},[313,984,327],{"class":319},[313,986,670],{"class":330},[313,988,673],{"class":323},[313,990,676],{"class":330},[313,992,407],{"class":323},[313,994,681],{"class":330},[313,996,997,999,1001,1003,1005,1007,1009,1011],{"class":315,"line":462},[313,998,320],{"class":319},[313,1000,688],{"class":323},[313,1002,327],{"class":319},[313,1004,670],{"class":330},[313,1006,673],{"class":323},[313,1008,676],{"class":330},[313,1010,407],{"class":323},[313,1012,701],{"class":330},[313,1014,1015],{"class":315,"line":470},[313,1016,486],{"emptyLinePlaceholder":485},[313,1018,1019,1021,1023,1025,1027,1029,1031,1033],{"class":315,"line":482},[313,1020,320],{"class":319},[313,1022,712],{"class":323},[313,1024,327],{"class":319},[313,1026,670],{"class":330},[313,1028,673],{"class":323},[313,1030,676],{"class":330},[313,1032,407],{"class":323},[313,1034,725],{"class":330},[313,1036,1037,1039,1041,1043,1045,1047,1049,1051],{"class":315,"line":489},[313,1038,320],{"class":319},[313,1040,732],{"class":323},[313,1042,327],{"class":319},[313,1044,670],{"class":330},[313,1046,673],{"class":323},[313,1048,676],{"class":330},[313,1050,407],{"class":323},[313,1052,745],{"class":330},[313,1054,1055],{"class":315,"line":506},[313,1056,486],{"emptyLinePlaceholder":485},[313,1058,1059,1061,1063,1065,1067],{"class":315,"line":519},[313,1060,320],{"class":319},[313,1062,831],{"class":323},[313,1064,327],{"class":319},[313,1066,836],{"class":330},[313,1068,840],{"class":839},[313,1070,1071,1073,1075,1077,1079],{"class":315,"line":528},[313,1072,320],{"class":319},[313,1074,847],{"class":323},[313,1076,327],{"class":319},[313,1078,852],{"class":330},[313,1080,840],{"class":839},[313,1082,1083,1085,1087,1089,1091,1093],{"class":315,"line":537},[313,1084,320],{"class":319},[313,1086,861],{"class":323},[313,1088,327],{"class":319},[313,1090,866],{"class":330},[313,1092,407],{"class":323},[313,1094,871],{"class":330},[313,1096,1097],{"class":315,"line":546},[313,1098,486],{"emptyLinePlaceholder":485},[313,1100,1101,1103,1105,1107,1109],{"class":315,"line":553},[313,1102,320],{"class":319},[313,1104,882],{"class":323},[313,1106,327],{"class":319},[313,1108,887],{"class":330},[313,1110,890],{"class":839},[313,1112,1114,1116,1118,1120,1123],{"class":315,"line":1113},16,[313,1115,320],{"class":319},[313,1117,897],{"class":323},[313,1119,327],{"class":319},[313,1121,1122],{"class":330},"\"XXXXXXXX\"",[313,1124,840],{"class":839},[259,1126,71],{"id":1127},"set-up-infrastructure-with-google-kubernetes-engine-gke",[12,1129,1130,1139,1140,1143],{},[28,1131,1134,1135,1138],{"href":1132,"rel":1133},"https://cloud.google.com/sdk/docs/install?hl=en",[],"Install and configure the Google Cloud SDK and ",[182,1136,1137],{},"gcloud"," CLI",", and install the ",[182,1141,1142],{},"gke-gcloud-auth-plugin"," plugin to authenticate against Google Cloud.",[304,1145,1147],{"className":306,"code":1146,"language":308,"meta":309,"style":309},"brew install --cask google-cloud-sdk\n\ngcloud components install gke-gcloud-auth-plugin\n\ngcloud auth login\n",[182,1148,1149,1163,1167,1179,1183],{"__ignoreMap":309},[313,1150,1151,1154,1157,1160],{"class":315,"line":316},[313,1152,1153],{"class":397},"brew",[313,1155,1156],{"class":330}," install",[313,1158,1159],{"class":400}," --cask",[313,1161,1162],{"class":330}," google-cloud-sdk\n",[313,1164,1165],{"class":315,"line":334},[313,1166,486],{"emptyLinePlaceholder":485},[313,1168,1169,1171,1174,1176],{"class":315,"line":347},[313,1170,1137],{"class":397},[313,1172,1173],{"class":330}," components",[313,1175,1156],{"class":330},[313,1177,1178],{"class":330}," gke-gcloud-auth-plugin\n",[313,1180,1181],{"class":315,"line":440},[313,1182,486],{"emptyLinePlaceholder":485},[313,1184,1185,1187,1190],{"class":315,"line":451},[313,1186,1137],{"class":397},[313,1188,1189],{"class":330}," auth",[313,1191,1192],{"class":330}," login\n",[12,1194,1195,1196,1198],{},"Create a new GKE cluster using the ",[182,1197,1137],{}," command, or follow the steps in the Google Cloud Console.",[304,1200,1202],{"className":306,"code":1201,"language":308,"meta":309,"style":309},"\nexport GCLOUD_PROJECT=group-community\nexport GCLOUD_CLUSTER=de-remote-development-1\n\ngcloud config set project $GCLOUD_PROJECT\n\n# Create cluster (modify for your needs)\ngcloud container clusters create $GCLOUD_CLUSTER \\\n    --release-channel stable \\\n    --zone us-central1-c \\\n    --project $GCLOUD_PROJECT\n\n# Verify cluster\ngcloud container clusters list\n\nNAME                     LOCATION         MASTER_VERSION   MASTER_IP       MACHINE_TYPE  NODE_VERSION       NUM_NODES  STATUS\nde-remote-development-1  us-central1-c    1.26.3-gke.1000  34.136.33.199   e2-medium     1.26.3-gke.1000    3          RUNNING\n\ngcloud container clusters get-credentials $GCLOUD_CLUSTER --zone us-central1-c --project $GCLOUD_PROJECT\nFetching cluster endpoint and auth data.\nkubeconfig entry generated for de-remote-development-1.\n",[182,1203,1204,1208,1220,1232,1236,1252,1256,1261,1280,1290,1300,1307,1311,1316,1327,1331,1357,1384,1389,1413,1433],{"__ignoreMap":309},[313,1205,1206],{"class":315,"line":316},[313,1207,486],{"emptyLinePlaceholder":485},[313,1209,1210,1212,1215,1217],{"class":315,"line":334},[313,1211,320],{"class":319},[313,1213,1214],{"class":323}," GCLOUD_PROJECT",[313,1216,327],{"class":319},[313,1218,1219],{"class":323},"group-community\n",[313,1221,1222,1224,1227,1229],{"class":315,"line":347},[313,1223,320],{"class":319},[313,1225,1226],{"class":323}," GCLOUD_CLUSTER",[313,1228,327],{"class":319},[313,1230,1231],{"class":323},"de-remote-development-1\n",[313,1233,1234],{"class":315,"line":440},[313,1235,486],{"emptyLinePlaceholder":485},[313,1237,1238,1240,1243,1246,1249],{"class":315,"line":451},[313,1239,1137],{"class":397},[313,1241,1242],{"class":330}," config",[313,1244,1245],{"class":330}," set",[313,1247,1248],{"class":330}," project",[313,1250,1251],{"class":323}," $GCLOUD_PROJECT\n",[313,1253,1254],{"class":315,"line":462},[313,1255,486],{"emptyLinePlaceholder":485},[313,1257,1258],{"class":315,"line":470},[313,1259,1260],{"class":839},"# Create cluster (modify for your needs)\n",[313,1262,1263,1265,1268,1271,1274,1277],{"class":315,"line":482},[313,1264,1137],{"class":397},[313,1266,1267],{"class":330}," container",[313,1269,1270],{"class":330}," clusters",[313,1272,1273],{"class":330}," create",[313,1275,1276],{"class":323}," $GCLOUD_CLUSTER ",[313,1278,1279],{"class":400},"\\\n",[313,1281,1282,1285,1288],{"class":315,"line":489},[313,1283,1284],{"class":400},"    --release-channel",[313,1286,1287],{"class":330}," stable",[313,1289,413],{"class":400},[313,1291,1292,1295,1298],{"class":315,"line":506},[313,1293,1294],{"class":400},"    --zone",[313,1296,1297],{"class":330}," us-central1-c",[313,1299,413],{"class":400},[313,1301,1302,1305],{"class":315,"line":519},[313,1303,1304],{"class":400},"    --project",[313,1306,1251],{"class":323},[313,1308,1309],{"class":315,"line":528},[313,1310,486],{"emptyLinePlaceholder":485},[313,1312,1313],{"class":315,"line":537},[313,1314,1315],{"class":839},"# Verify cluster\n",[313,1317,1318,1320,1322,1324],{"class":315,"line":546},[313,1319,1137],{"class":397},[313,1321,1267],{"class":330},[313,1323,1270],{"class":330},[313,1325,1326],{"class":330}," list\n",[313,1328,1329],{"class":315,"line":553},[313,1330,486],{"emptyLinePlaceholder":485},[313,1332,1333,1336,1339,1342,1345,1348,1351,1354],{"class":315,"line":1113},[313,1334,1335],{"class":397},"NAME",[313,1337,1338],{"class":330},"                     LOCATION",[313,1340,1341],{"class":330},"         MASTER_VERSION",[313,1343,1344],{"class":330},"   MASTER_IP",[313,1346,1347],{"class":330},"       MACHINE_TYPE",[313,1349,1350],{"class":330},"  NODE_VERSION",[313,1352,1353],{"class":330},"       NUM_NODES",[313,1355,1356],{"class":330},"  STATUS\n",[313,1358,1360,1363,1366,1369,1372,1375,1378,1381],{"class":315,"line":1359},17,[313,1361,1362],{"class":397},"de-remote-development-1",[313,1364,1365],{"class":330},"  us-central1-c",[313,1367,1368],{"class":330},"    1.26.3-gke.1000",[313,1370,1371],{"class":400},"  34.136.33.199",[313,1373,1374],{"class":330},"   e2-medium",[313,1376,1377],{"class":330},"     1.26.3-gke.1000",[313,1379,1380],{"class":400},"    3",[313,1382,1383],{"class":330},"          RUNNING\n",[313,1385,1387],{"class":315,"line":1386},18,[313,1388,486],{"emptyLinePlaceholder":485},[313,1390,1392,1394,1396,1398,1401,1403,1406,1408,1411],{"class":315,"line":1391},19,[313,1393,1137],{"class":397},[313,1395,1267],{"class":330},[313,1397,1270],{"class":330},[313,1399,1400],{"class":330}," get-credentials",[313,1402,1276],{"class":323},[313,1404,1405],{"class":400},"--zone",[313,1407,1297],{"class":330},[313,1409,1410],{"class":400}," --project",[313,1412,1251],{"class":323},[313,1414,1416,1419,1422,1425,1428,1430],{"class":315,"line":1415},20,[313,1417,1418],{"class":397},"Fetching",[313,1420,1421],{"class":330}," cluster",[313,1423,1424],{"class":330}," endpoint",[313,1426,1427],{"class":330}," and",[313,1429,1189],{"class":330},[313,1431,1432],{"class":330}," data.\n",[313,1434,1436,1439,1442,1445,1448],{"class":315,"line":1435},21,[313,1437,1438],{"class":397},"kubeconfig",[313,1440,1441],{"class":330}," entry",[313,1443,1444],{"class":330}," generated",[313,1446,1447],{"class":330}," for",[313,1449,1450],{"class":330}," de-remote-development-1.\n",[200,1452,1453,1465,1468,1477,1486],{},[25,1454,1455,1456,1464],{},"The setup requires the ",[28,1457,1460,1463],{"href":1458,"rel":1459},"https://cloud.google.com/kubernetes-engine/docs/concepts/access-control?hl=en#recommendations",[],[182,1461,1462],{},"Kubernetes Engine Admin"," role in Google IAM"," to create ClusterRoleBindings.",[25,1466,1467],{},"Create a new Kubernetes cluster (do not use Autopilot).",[25,1469,1470,1471,1476],{},"Ensure that ",[28,1472,1475],{"href":1473,"rel":1474},"https://cloud.google.com/kubernetes-engine/docs/concepts/cluster-autoscaler?hl=en",[],"cluster autoscaling"," is enabled in the GKE cluster.",[25,1478,1479,1480,1485],{},"Verify that a ",[28,1481,1484],{"href":1482,"rel":1483},"https://cloud.google.com/kubernetes-engine/docs/concepts/persistent-volumes?hl=en#storageclasses",[],"default Storage Class"," has been defined.",[25,1487,1488,1489,1494,1495,1497],{},"Install an Ingress controller, for example ",[28,1490,1493],{"href":1491,"rel":1492},"https://kubernetes.github.io/ingress-nginx/deploy/#gce-gke",[],"ingress-nginx",". Follow the documentation and run the following commands to install ",[182,1496,1493],{}," into the Kubernetes cluster.",[304,1499,1501],{"className":306,"code":1500,"language":308,"meta":309,"style":309},"kubectl create clusterrolebinding cluster-admin-binding \\\n  --clusterrole cluster-admin \\\n  --user $(gcloud config get-value account)\n\nkubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.7.1/deploy/static/provider/cloud/deploy.yaml\n",[182,1502,1503,1517,1527,1548,1552],{"__ignoreMap":309},[313,1504,1505,1507,1509,1512,1515],{"class":315,"line":316},[313,1506,243],{"class":397},[313,1508,1273],{"class":330},[313,1510,1511],{"class":330}," clusterrolebinding",[313,1513,1514],{"class":330}," cluster-admin-binding",[313,1516,413],{"class":400},[313,1518,1519,1522,1525],{"class":315,"line":334},[313,1520,1521],{"class":400},"  --clusterrole",[313,1523,1524],{"class":330}," cluster-admin",[313,1526,413],{"class":400},[313,1528,1529,1532,1535,1537,1539,1542,1545],{"class":315,"line":347},[313,1530,1531],{"class":400},"  --user",[313,1533,1534],{"class":323}," $(",[313,1536,1137],{"class":397},[313,1538,1242],{"class":330},[313,1540,1541],{"class":330}," get-value",[313,1543,1544],{"class":330}," account",[313,1546,1547],{"class":323},")\n",[313,1549,1550],{"class":315,"line":440},[313,1551,486],{"emptyLinePlaceholder":485},[313,1553,1554,1556,1559,1562],{"class":315,"line":451},[313,1555,243],{"class":397},[313,1557,1558],{"class":330}," apply",[313,1560,1561],{"class":400}," -f",[313,1563,1564],{"class":330}," https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.7.1/deploy/static/provider/cloud/deploy.yaml\n",[12,1566,1567,1568,276,1570,1572],{},"Print the external IP for the DNS records, and update wildcard DNS (",[182,1569,275],{},[182,1571,279],{},").",[304,1574,1576],{"className":306,"code":1575,"language":308,"meta":309,"style":309},"gcloud container clusters list\n\nkubectl get services -A\n",[182,1577,1578,1588,1592],{"__ignoreMap":309},[313,1579,1580,1582,1584,1586],{"class":315,"line":316},[313,1581,1137],{"class":397},[313,1583,1267],{"class":330},[313,1585,1270],{"class":330},[313,1587,1326],{"class":330},[313,1589,1590],{"class":315,"line":334},[313,1591,486],{"emptyLinePlaceholder":485},[313,1593,1594,1596,1599,1602],{"class":315,"line":347},[313,1595,243],{"class":397},[313,1597,1598],{"class":330}," get",[313,1600,1601],{"class":330}," services",[313,1603,1604],{"class":400}," -A\n",[259,1606,77],{"id":1607},"set-up-infrastructure-with-amazon-elastic-kubernetes-service-eks",[12,1609,1610,1611,1616,1617,1625,1626,1628,1629,1634],{},"Creating an Amazon EKS cluster requires ",[28,1612,1615],{"href":1613,"rel":1614},"https://docs.aws.amazon.com/eks/latest/userguide/create-cluster.html",[],"cluster IAM roles",". You can the ",[28,1618,1621,1624],{"href":1619,"rel":1620},"https://eksctl.io/",[],[182,1622,1623],{},"eksctl"," CLI for Amazon EKS",", which automatically creates the roles. ",[182,1627,1623],{}," ",[28,1630,1633],{"href":1631,"rel":1632},"https://github.com/weaveworks/eksctl/blob/main/README.md#prerequisite",[],"requires the AWS IAM Authenticator for Kubernetes",", which will get pulled with Homebrew automatically on macOS.",[304,1636,1638],{"className":306,"code":1637,"language":308,"meta":309,"style":309},"brew install eksctl awscli aws-iam-authenticator\naws configure\n\neksctl create cluster --name remote-dev \\\n    --region us-west-2 \\\n    --node-type m5.xlarge \\\n    --nodes 3 \\\n    --nodes-min=1 \\\n    --nodes-max=4 \\\n    --version=1.26 \\\n    --asg-access\n",[182,1639,1640,1655,1663,1667,1683,1693,1703,1713,1720,1727,1734],{"__ignoreMap":309},[313,1641,1642,1644,1646,1649,1652],{"class":315,"line":316},[313,1643,1153],{"class":397},[313,1645,1156],{"class":330},[313,1647,1648],{"class":330}," eksctl",[313,1650,1651],{"class":330}," awscli",[313,1653,1654],{"class":330}," aws-iam-authenticator\n",[313,1656,1657,1660],{"class":315,"line":334},[313,1658,1659],{"class":397},"aws",[313,1661,1662],{"class":330}," configure\n",[313,1664,1665],{"class":315,"line":347},[313,1666,486],{"emptyLinePlaceholder":485},[313,1668,1669,1671,1673,1675,1678,1681],{"class":315,"line":440},[313,1670,1623],{"class":397},[313,1672,1273],{"class":330},[313,1674,1421],{"class":330},[313,1676,1677],{"class":400}," --name",[313,1679,1680],{"class":330}," remote-dev",[313,1682,413],{"class":400},[313,1684,1685,1688,1691],{"class":315,"line":451},[313,1686,1687],{"class":400},"    --region",[313,1689,1690],{"class":330}," us-west-2",[313,1692,413],{"class":400},[313,1694,1695,1698,1701],{"class":315,"line":462},[313,1696,1697],{"class":400},"    --node-type",[313,1699,1700],{"class":330}," m5.xlarge",[313,1702,413],{"class":400},[313,1704,1705,1708,1711],{"class":315,"line":470},[313,1706,1707],{"class":400},"    --nodes",[313,1709,1710],{"class":400}," 3",[313,1712,413],{"class":400},[313,1714,1715,1718],{"class":315,"line":482},[313,1716,1717],{"class":400},"    --nodes-min=1",[313,1719,413],{"class":400},[313,1721,1722,1725],{"class":315,"line":489},[313,1723,1724],{"class":400},"    --nodes-max=4",[313,1726,413],{"class":400},[313,1728,1729,1732],{"class":315,"line":506},[313,1730,1731],{"class":400},"    --version=1.26",[313,1733,413],{"class":400},[313,1735,1736],{"class":315,"line":519},[313,1737,1738],{"class":400},"    --asg-access\n",[12,1740,1741,1742,1754,1755,1760,1761,1766,1767,1772,1773,1781],{},"The eksctl command uses the ",[28,1743,1746,1749,1750,1753],{"href":1744,"rel":1745},"https://eksctl.io/usage/autoscaling/",[],[182,1747,1748],{},"--asg-access",", ",[182,1751,1752],{},"--nodes-min/max"," parameters for auto-scaling",". The autoscaler requires ",[28,1756,1759],{"href":1757,"rel":1758},"https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/aws/README.md",[],"additional configuration steps",", alternatively ",[28,1762,1765],{"href":1763,"rel":1764},"https://karpenter.sh/docs/getting-started/getting-started-with-karpenter/",[],"Karpenter is supported in Amazon EKS",". Review the ",[28,1768,1771],{"href":1769,"rel":1770},"https://docs.aws.amazon.com/eks/latest/userguide/autoscaling.html",[],"autoscaling documentation",", and ",[28,1774,1777,1778],{"href":1775,"rel":1776},"https://docs.aws.amazon.com/eks/latest/userguide/storage-classes.html",[],"default Storage Class ",[182,1779,1780],{},"gp2"," fulfilling the requirements. The Kubernetes configuration is automatically updated locally.",[12,1783,1784,1785,1790,1791,1497],{},"Install the ",[28,1786,1789],{"href":1787,"rel":1788},"https://kubernetes.github.io/ingress-nginx/deploy/#aws",[],"Nginx Ingress controller for EKS",". Follow the documentation and run the following command to install ",[182,1792,1493],{},[304,1794,1796],{"className":306,"code":1795,"language":308,"meta":309,"style":309},"kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.8.0/deploy/static/provider/aws/deploy.yaml\n",[182,1797,1798],{"__ignoreMap":309},[313,1799,1800,1802,1804,1806],{"class":315,"line":316},[313,1801,243],{"class":397},[313,1803,1558],{"class":330},[313,1805,1561],{"class":400},[313,1807,1808],{"class":330}," https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.8.0/deploy/static/provider/aws/deploy.yaml\n",[12,1810,1567,1811,276,1813,1572],{},[182,1812,275],{},[182,1814,279],{},[304,1816,1818],{"className":306,"code":1817,"language":308,"meta":309,"style":309},"eksctl get cluster --region us-west-2 --name remote-dev\n\nkubectl get services -A\n",[182,1819,1820,1838,1842],{"__ignoreMap":309},[313,1821,1822,1824,1826,1828,1831,1833,1835],{"class":315,"line":316},[313,1823,1623],{"class":397},[313,1825,1598],{"class":330},[313,1827,1421],{"class":330},[313,1829,1830],{"class":400}," --region",[313,1832,1690],{"class":330},[313,1834,1677],{"class":400},[313,1836,1837],{"class":330}," remote-dev\n",[313,1839,1840],{"class":315,"line":334},[313,1841,486],{"emptyLinePlaceholder":485},[313,1843,1844,1846,1848,1850],{"class":315,"line":347},[313,1845,243],{"class":397},[313,1847,1598],{"class":330},[313,1849,1601],{"class":330},[313,1851,1604],{"class":400},[259,1853,83],{"id":1854},"set-up-infrastructure-with-azure-managed-kubernetes-service-aks",[12,1856,1857,1858,931],{},"Install ",[28,1859,1862],{"href":1860,"rel":1861},"https://learn.microsoft.com/en-us/azure/aks/learn/quick-kubernetes-deploy-cli",[],"Azure CLI",[304,1864,1866],{"className":306,"code":1865,"language":308,"meta":309,"style":309},"brew install azure-cli\n\naz login\n",[182,1867,1868,1877,1881],{"__ignoreMap":309},[313,1869,1870,1872,1874],{"class":315,"line":316},[313,1871,1153],{"class":397},[313,1873,1156],{"class":330},[313,1875,1876],{"class":330}," azure-cli\n",[313,1878,1879],{"class":315,"line":334},[313,1880,486],{"emptyLinePlaceholder":485},[313,1882,1883,1886],{"class":315,"line":347},[313,1884,1885],{"class":397},"az",[313,1887,1192],{"class":330},[12,1889,1890,1891,1896,1897,1905,1906,1908],{},"Review the documentation for the ",[28,1892,1895],{"href":1893,"rel":1894},"https://learn.microsoft.com/en-us/azure/aks/cluster-autoscaler",[],"cluster autoscaler in AKS"," and the ",[28,1898,1901,1902],{"href":1899,"rel":1900},"https://learn.microsoft.com/en-us/azure/aks/concepts-storage#storage-classes",[],"default Storage Class being ",[182,1903,1904],{},"managed-csi",", create a new resource group, and create a new Kubernetes cluster. Download the Kubernetes configuration to continue with the ",[182,1907,243],{}," commands.",[304,1910,1912],{"className":306,"code":1911,"language":308,"meta":309,"style":309},"az group create --name remote-dev-rg --location eastus\n\naz aks create \\\n--resource-group remote-dev-rg \\\n--name remote-dev \\\n--node-count 1 \\\n--vm-set-type VirtualMachineScaleSets \\\n--load-balancer-sku standard \\\n--enable-cluster-autoscaler \\\n--min-count 1 \\\n--max-count 3\n\naz aks get-credentials --resource-group remote-dev-rg --name remote-dev\n",[182,1913,1914,1934,1938,1949,1959,1969,1979,1989,1999,2006,2015,2023,2027],{"__ignoreMap":309},[313,1915,1916,1918,1921,1923,1925,1928,1931],{"class":315,"line":316},[313,1917,1885],{"class":397},[313,1919,1920],{"class":330}," group",[313,1922,1273],{"class":330},[313,1924,1677],{"class":400},[313,1926,1927],{"class":330}," remote-dev-rg",[313,1929,1930],{"class":400}," --location",[313,1932,1933],{"class":330}," eastus\n",[313,1935,1936],{"class":315,"line":334},[313,1937,486],{"emptyLinePlaceholder":485},[313,1939,1940,1942,1945,1947],{"class":315,"line":347},[313,1941,1885],{"class":397},[313,1943,1944],{"class":330}," aks",[313,1946,1273],{"class":330},[313,1948,413],{"class":400},[313,1950,1951,1954,1957],{"class":315,"line":440},[313,1952,1953],{"class":323},"--resource-group ",[313,1955,1956],{"class":330},"remote-dev-rg",[313,1958,413],{"class":400},[313,1960,1961,1964,1967],{"class":315,"line":451},[313,1962,1963],{"class":323},"--name ",[313,1965,1966],{"class":330},"remote-dev",[313,1968,413],{"class":400},[313,1970,1971,1974,1977],{"class":315,"line":462},[313,1972,1973],{"class":323},"--node-count ",[313,1975,1976],{"class":400},"1",[313,1978,413],{"class":400},[313,1980,1981,1984,1987],{"class":315,"line":470},[313,1982,1983],{"class":323},"--vm-set-type ",[313,1985,1986],{"class":330},"VirtualMachineScaleSets",[313,1988,413],{"class":400},[313,1990,1991,1994,1997],{"class":315,"line":482},[313,1992,1993],{"class":323},"--load-balancer-sku ",[313,1995,1996],{"class":330},"standard",[313,1998,413],{"class":400},[313,2000,2001,2004],{"class":315,"line":489},[313,2002,2003],{"class":323},"--enable-cluster-autoscaler ",[313,2005,1279],{"class":400},[313,2007,2008,2011,2013],{"class":315,"line":506},[313,2009,2010],{"class":323},"--min-count ",[313,2012,1976],{"class":400},[313,2014,413],{"class":400},[313,2016,2017,2020],{"class":315,"line":519},[313,2018,2019],{"class":323},"--max-count ",[313,2021,2022],{"class":400},"3\n",[313,2024,2025],{"class":315,"line":528},[313,2026,486],{"emptyLinePlaceholder":485},[313,2028,2029,2031,2033,2035,2038,2040,2042],{"class":315,"line":537},[313,2030,1885],{"class":397},[313,2032,1944],{"class":330},[313,2034,1400],{"class":330},[313,2036,2037],{"class":400}," --resource-group",[313,2039,1927],{"class":330},[313,2041,1677],{"class":400},[313,2043,1837],{"class":330},[12,2045,1784,2046,1494,2051,1497],{},[28,2047,2050],{"href":2048,"rel":2049},"https://learn.microsoft.com/en-us/azure/aks/ingress-basic?tabs=azure-cli#basic-configuration",[],"Nginx ingress controller in AKS",[182,2052,1493],{},[304,2054,2056],{"className":306,"code":2055,"language":308,"meta":309,"style":309},"NAMESPACE=ingress-basic\n\nhelm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx\nhelm repo update\n\nhelm install ingress-nginx ingress-nginx/ingress-nginx \\\n  --create-namespace \\\n  --namespace $NAMESPACE \\\n  --set controller.service.annotations.\"service\\.beta\\.kubernetes\\.io/azure-load-balancer-health-probe-request-path\"=/healthz\n",[182,2057,2058,2068,2072,2088,2097,2101,2114,2121,2131],{"__ignoreMap":309},[313,2059,2060,2063,2065],{"class":315,"line":316},[313,2061,2062],{"class":323},"NAMESPACE",[313,2064,327],{"class":319},[313,2066,2067],{"class":330},"ingress-basic\n",[313,2069,2070],{"class":315,"line":334},[313,2071,486],{"emptyLinePlaceholder":485},[313,2073,2074,2076,2079,2082,2085],{"class":315,"line":347},[313,2075,247],{"class":397},[313,2077,2078],{"class":330}," repo",[313,2080,2081],{"class":330}," add",[313,2083,2084],{"class":330}," ingress-nginx",[313,2086,2087],{"class":330}," https://kubernetes.github.io/ingress-nginx\n",[313,2089,2090,2092,2094],{"class":315,"line":440},[313,2091,247],{"class":397},[313,2093,2078],{"class":330},[313,2095,2096],{"class":330}," update\n",[313,2098,2099],{"class":315,"line":451},[313,2100,486],{"emptyLinePlaceholder":485},[313,2102,2103,2105,2107,2109,2112],{"class":315,"line":462},[313,2104,247],{"class":397},[313,2106,1156],{"class":330},[313,2108,2084],{"class":330},[313,2110,2111],{"class":330}," ingress-nginx/ingress-nginx",[313,2113,413],{"class":400},[313,2115,2116,2119],{"class":315,"line":470},[313,2117,2118],{"class":400},"  --create-namespace",[313,2120,413],{"class":400},[313,2122,2123,2126,2129],{"class":315,"line":482},[313,2124,2125],{"class":400},"  --namespace",[313,2127,2128],{"class":323}," $NAMESPACE ",[313,2130,1279],{"class":400},[313,2132,2133,2136],{"class":315,"line":489},[313,2134,2135],{"class":400},"  --set",[313,2137,2138],{"class":330}," controller.service.annotations.\"service\\.beta\\.kubernetes\\.io/azure-load-balancer-health-probe-request-path\"=/healthz\n",[12,2140,1567,2141,276,2143,1572],{},[182,2142,275],{},[182,2144,279],{},[304,2146,2148],{"className":306,"code":2147,"language":308,"meta":309,"style":309},"kubectl get services --namespace ingress-basic -o wide -w ingress-nginx-controller\n\nkubectl get services -A\n",[182,2149,2150,2176,2180],{"__ignoreMap":309},[313,2151,2152,2154,2156,2158,2161,2164,2167,2170,2173],{"class":315,"line":316},[313,2153,243],{"class":397},[313,2155,1598],{"class":330},[313,2157,1601],{"class":330},[313,2159,2160],{"class":400}," --namespace",[313,2162,2163],{"class":330}," ingress-basic",[313,2165,2166],{"class":400}," -o",[313,2168,2169],{"class":330}," wide",[313,2171,2172],{"class":400}," -w",[313,2174,2175],{"class":330}," ingress-nginx-controller\n",[313,2177,2178],{"class":315,"line":334},[313,2179,486],{"emptyLinePlaceholder":485},[313,2181,2182,2184,2186,2188],{"class":315,"line":347},[313,2183,243],{"class":397},[313,2185,1598],{"class":330},[313,2187,1601],{"class":330},[313,2189,1604],{"class":400},[259,2191,89],{"id":2192},"set-up-infrastructure-with-civo-cloud-kubernetes",[12,2194,2195,2196,2201],{},"Install and configure the ",[28,2197,2200],{"href":2198,"rel":2199},"https://www.civo.com/docs/kubernetes/create-a-cluster#creating-a-cluster-using-civo-cli",[],"Civo CLI",", and create a Kubernetes cluster using 2 nodes, 4 CPUs, 8 GB RAM.",[304,2203,2205],{"className":306,"code":2204,"language":308,"meta":309,"style":309},"civo kubernetes create remote-dev -n 2 -s g4s.kube.large\n\ncivo kubernetes config remote-dev --save\nkubectl config use-context remote-dev\n",[182,2206,2207,2231,2235,2248],{"__ignoreMap":309},[313,2208,2209,2212,2215,2217,2219,2222,2225,2228],{"class":315,"line":316},[313,2210,2211],{"class":397},"civo",[313,2213,2214],{"class":330}," kubernetes",[313,2216,1273],{"class":330},[313,2218,1680],{"class":330},[313,2220,2221],{"class":400}," -n",[313,2223,2224],{"class":400}," 2",[313,2226,2227],{"class":400}," -s",[313,2229,2230],{"class":330}," g4s.kube.large\n",[313,2232,2233],{"class":315,"line":334},[313,2234,486],{"emptyLinePlaceholder":485},[313,2236,2237,2239,2241,2243,2245],{"class":315,"line":347},[313,2238,2211],{"class":397},[313,2240,2214],{"class":330},[313,2242,1242],{"class":330},[313,2244,1680],{"class":330},[313,2246,2247],{"class":400}," --save\n",[313,2249,2250,2252,2254,2257],{"class":315,"line":440},[313,2251,243],{"class":397},[313,2253,1242],{"class":330},[313,2255,2256],{"class":330}," use-context",[313,2258,1837],{"class":330},[12,2260,2261,2262,2266],{},"You have full permissions on the cluster to create ClusterRoleBindings. The ",[28,2263,1484],{"href":2264,"rel":2265},"https://www.civo.com/docs/kubernetes/kubernetes-volumes#creating-a-persistent-volume-claim-pvc",[]," is set to 'civo-volume'.",[12,2268,1784,2269,1790,2274,1497],{},[28,2270,2273],{"href":2271,"rel":2272},"https://kubernetes.github.io/ingress-nginx/deploy/#quick-start",[],"Nginx Ingress controller using Helm",[182,2275,1493],{},[304,2277,2279],{"className":306,"code":2278,"language":308,"meta":309,"style":309},"helm upgrade --install ingress-nginx ingress-nginx \\\n  --repo https://kubernetes.github.io/ingress-nginx \\\n  --namespace ingress-nginx --create-namespace\n\n",[182,2280,2281,2297,2307],{"__ignoreMap":309},[313,2282,2283,2285,2288,2291,2293,2295],{"class":315,"line":316},[313,2284,247],{"class":397},[313,2286,2287],{"class":330}," upgrade",[313,2289,2290],{"class":400}," --install",[313,2292,2084],{"class":330},[313,2294,2084],{"class":330},[313,2296,413],{"class":400},[313,2298,2299,2302,2305],{"class":315,"line":334},[313,2300,2301],{"class":400},"  --repo",[313,2303,2304],{"class":330}," https://kubernetes.github.io/ingress-nginx",[313,2306,413],{"class":400},[313,2308,2309,2311,2313],{"class":315,"line":347},[313,2310,2125],{"class":400},[313,2312,2084],{"class":330},[313,2314,2315],{"class":400}," --create-namespace\n",[12,2317,1567,2318,276,2320,1572],{},[182,2319,275],{},[182,2321,279],{},[304,2323,2325],{"className":306,"code":2324,"language":308,"meta":309,"style":309},"civo kubernetes show remote-dev\n\nkubectl get services -A\n",[182,2326,2327,2338,2342],{"__ignoreMap":309},[313,2328,2329,2331,2333,2336],{"class":315,"line":316},[313,2330,2211],{"class":397},[313,2332,2214],{"class":330},[313,2334,2335],{"class":330}," show",[313,2337,1837],{"class":330},[313,2339,2340],{"class":315,"line":334},[313,2341,486],{"emptyLinePlaceholder":485},[313,2343,2344,2346,2348,2350],{"class":315,"line":347},[313,2345,243],{"class":397},[313,2347,1598],{"class":330},[313,2349,1601],{"class":330},[313,2351,1604],{"class":400},[259,2353,95],{"id":2354},"set-up-infrastructure-with-self-managed-kubernetes",[12,2356,2357,2358,2361,2362,2366,2367,276,2369,2371],{},"The process follows similar steps, requiring a user with permission to create ",[182,2359,2360],{},"ClusterRoleBinding"," resources. The ",[28,2363,2365],{"href":2271,"rel":2364},[],"Nginx Ingress controller"," is the fastest path forward. Once the cluster is ready, print the load balancer IP for the DNS records, and create/update A/AAAA record for wildcard DNS (",[182,2368,275],{},[182,2370,279],{},") pointing to the load balancer IP.",[161,2373,101],{"id":2374},"workspaces-proxy-installation-into-kubernetes",[12,2376,2377],{},[759,2378,2379],{},"After completing the Kubernetes cluster setup with one of your preferred providers, please continue with the next steps.",[12,2381,2382,2383,1572],{},"Add the Helm repository for the workspaces proxy (it is using the ",[28,2384,2387],{"href":2385,"rel":2386},"https://docs.gitlab.com/user/packages/helm_repository/",[],"Helm charts feature in the GitLab package registry",[304,2389,2391],{"className":306,"code":2390,"language":308,"meta":309,"style":309},"helm repo add gitlab-workspaces-proxy \\\nhttps://gitlab.com/api/v4/projects/gitlab-org%2fremote-development%2fgitlab-workspaces-proxy/packages/helm/devel\n",[182,2392,2393,2406],{"__ignoreMap":309},[313,2394,2395,2397,2399,2401,2404],{"class":315,"line":316},[313,2396,247],{"class":397},[313,2398,2078],{"class":330},[313,2400,2081],{"class":330},[313,2402,2403],{"class":330}," gitlab-workspaces-proxy",[313,2405,413],{"class":400},[313,2407,2408],{"class":315,"line":334},[313,2409,2410],{"class":323},"https://gitlab.com/api/v4/projects/gitlab-org%2fremote-development%2fgitlab-workspaces-proxy/packages/helm/devel\n",[12,2412,2413,2414,2419,2420,2423],{},"Install the gitlab-workspaces-proxy, and optionally ",[28,2415,2418],{"href":2416,"rel":2417},"https://gitlab.com/gitlab-org/remote-development/gitlab-workspaces-proxy/-/blob/main/helm/Chart.yaml",[],"specify the most current chart version",". If you are using a different ingress controller than Nginx, you need to change the ",[182,2421,2422],{},"ingress.className"," key. Re-run the command when new TLS certificates need to be installed.",[304,2425,2427],{"className":306,"code":2426,"language":308,"meta":309,"style":309},"helm repo update\n\nhelm upgrade --install gitlab-workspaces-proxy \\\n  gitlab-workspaces-proxy/gitlab-workspaces-proxy \\\n  --version 0.1.6 \\\n  --namespace=gitlab-workspaces \\\n  --create-namespace \\\n  --set=\"auth.client_id=${CLIENT_ID}\" \\\n  --set=\"auth.client_secret=${CLIENT_SECRET}\" \\\n  --set=\"auth.host=${GITLAB_URL}\" \\\n  --set=\"auth.redirect_uri=${REDIRECT_URI}\" \\\n  --set=\"auth.signing_key=${SIGNING_KEY}\" \\\n  --set=\"ingress.host.workspaceDomain=${GITLAB_WORKSPACES_PROXY_DOMAIN}\" \\\n--set=\"ingress.host.wildcardDomain=${GITLAB_WORKSPACES_WILDCARD_DOMAIN}\" \\\n  --set=\"ingress.tls.workspaceDomainCert=$(cat ${WORKSPACES_DOMAIN_CERT})\" \\\n  --set=\"ingress.tls.workspaceDomainKey=$(cat ${WORKSPACES_DOMAIN_KEY})\" \\\n  --set=\"ingress.tls.wildcardDomainCert=$(cat ${WILDCARD_DOMAIN_CERT})\" \\\n  --set=\"ingress.tls.wildcardDomainKey=$(cat ${WILDCARD_DOMAIN_KEY})\" \\\n  --set=\"ingress.className=nginx\"\n",[182,2428,2429,2437,2441,2453,2460,2470,2477,2483,2498,2512,2526,2540,2553,2566,2580,2601,2619,2637,2655],{"__ignoreMap":309},[313,2430,2431,2433,2435],{"class":315,"line":316},[313,2432,247],{"class":397},[313,2434,2078],{"class":330},[313,2436,2096],{"class":330},[313,2438,2439],{"class":315,"line":334},[313,2440,486],{"emptyLinePlaceholder":485},[313,2442,2443,2445,2447,2449,2451],{"class":315,"line":347},[313,2444,247],{"class":397},[313,2446,2287],{"class":330},[313,2448,2290],{"class":400},[313,2450,2403],{"class":330},[313,2452,413],{"class":400},[313,2454,2455,2458],{"class":315,"line":440},[313,2456,2457],{"class":330},"  gitlab-workspaces-proxy/gitlab-workspaces-proxy",[313,2459,413],{"class":400},[313,2461,2462,2465,2468],{"class":315,"line":451},[313,2463,2464],{"class":400},"  --version",[313,2466,2467],{"class":400}," 0.1.6",[313,2469,413],{"class":400},[313,2471,2472,2475],{"class":315,"line":462},[313,2473,2474],{"class":400},"  --namespace=gitlab-workspaces",[313,2476,413],{"class":400},[313,2478,2479,2481],{"class":315,"line":470},[313,2480,2118],{"class":400},[313,2482,413],{"class":400},[313,2484,2485,2488,2491,2494,2496],{"class":315,"line":482},[313,2486,2487],{"class":400},"  --set=",[313,2489,2490],{"class":330},"\"auth.client_id=${",[313,2492,2493],{"class":323},"CLIENT_ID",[313,2495,410],{"class":330},[313,2497,413],{"class":400},[313,2499,2500,2502,2505,2508,2510],{"class":315,"line":489},[313,2501,2487],{"class":400},[313,2503,2504],{"class":330},"\"auth.client_secret=${",[313,2506,2507],{"class":323},"CLIENT_SECRET",[313,2509,410],{"class":330},[313,2511,413],{"class":400},[313,2513,2514,2516,2519,2522,2524],{"class":315,"line":506},[313,2515,2487],{"class":400},[313,2517,2518],{"class":330},"\"auth.host=${",[313,2520,2521],{"class":323},"GITLAB_URL",[313,2523,410],{"class":330},[313,2525,413],{"class":400},[313,2527,2528,2530,2533,2536,2538],{"class":315,"line":519},[313,2529,2487],{"class":400},[313,2531,2532],{"class":330},"\"auth.redirect_uri=${",[313,2534,2535],{"class":323},"REDIRECT_URI",[313,2537,410],{"class":330},[313,2539,413],{"class":400},[313,2541,2542,2544,2547,2549,2551],{"class":315,"line":528},[313,2543,2487],{"class":400},[313,2545,2546],{"class":330},"\"auth.signing_key=${",[313,2548,818],{"class":323},[313,2550,410],{"class":330},[313,2552,413],{"class":400},[313,2554,2555,2557,2560,2562,2564],{"class":315,"line":537},[313,2556,2487],{"class":400},[313,2558,2559],{"class":330},"\"ingress.host.workspaceDomain=${",[313,2561,407],{"class":323},[313,2563,410],{"class":330},[313,2565,413],{"class":400},[313,2567,2568,2571,2574,2576,2578],{"class":315,"line":546},[313,2569,2570],{"class":323},"--set=",[313,2572,2573],{"class":330},"\"ingress.host.wildcardDomain=${",[313,2575,499],{"class":323},[313,2577,410],{"class":330},[313,2579,413],{"class":400},[313,2581,2582,2584,2587,2590,2593,2596,2599],{"class":315,"line":553},[313,2583,2487],{"class":400},[313,2585,2586],{"class":330},"\"ingress.tls.workspaceDomainCert=$(",[313,2588,2589],{"class":397},"cat",[313,2591,2592],{"class":330}," ${",[313,2594,2595],{"class":323},"WORKSPACES_DOMAIN_CERT",[313,2597,2598],{"class":330},"})\"",[313,2600,413],{"class":400},[313,2602,2603,2605,2608,2610,2612,2615,2617],{"class":315,"line":1113},[313,2604,2487],{"class":400},[313,2606,2607],{"class":330},"\"ingress.tls.workspaceDomainKey=$(",[313,2609,2589],{"class":397},[313,2611,2592],{"class":330},[313,2613,2614],{"class":323},"WORKSPACES_DOMAIN_KEY",[313,2616,2598],{"class":330},[313,2618,413],{"class":400},[313,2620,2621,2623,2626,2628,2630,2633,2635],{"class":315,"line":1359},[313,2622,2487],{"class":400},[313,2624,2625],{"class":330},"\"ingress.tls.wildcardDomainCert=$(",[313,2627,2589],{"class":397},[313,2629,2592],{"class":330},[313,2631,2632],{"class":323},"WILDCARD_DOMAIN_CERT",[313,2634,2598],{"class":330},[313,2636,413],{"class":400},[313,2638,2639,2641,2644,2646,2648,2651,2653],{"class":315,"line":1386},[313,2640,2487],{"class":400},[313,2642,2643],{"class":330},"\"ingress.tls.wildcardDomainKey=$(",[313,2645,2589],{"class":397},[313,2647,2592],{"class":330},[313,2649,2650],{"class":323},"WILDCARD_DOMAIN_KEY",[313,2652,2598],{"class":330},[313,2654,413],{"class":400},[313,2656,2657,2659],{"class":315,"line":1391},[313,2658,2487],{"class":400},[313,2660,2661],{"class":330},"\"ingress.className=nginx\"\n",[12,2663,2664,2665,2668],{},"The chart installs and configures the ingress automatically. You can verify the setup by getting the ",[182,2666,2667],{},"Ingress"," resource type:",[304,2670,2672],{"className":306,"code":2671,"language":308,"meta":309,"style":309},"kubectl get ingress -n gitlab-workspaces\n\nNAME                      CLASS   HOSTS                             ADDRESS   PORTS     AGE\ngitlab-workspaces-proxy   nginx   remote-dev.dev,*.remote-dev.dev             80, 443   9s\n",[182,2673,2674,2688,2692,2711],{"__ignoreMap":309},[313,2675,2676,2678,2680,2683,2685],{"class":315,"line":316},[313,2677,243],{"class":397},[313,2679,1598],{"class":330},[313,2681,2682],{"class":330}," ingress",[313,2684,2221],{"class":400},[313,2686,2687],{"class":330}," gitlab-workspaces\n",[313,2689,2690],{"class":315,"line":334},[313,2691,486],{"emptyLinePlaceholder":485},[313,2693,2694,2696,2699,2702,2705,2708],{"class":315,"line":347},[313,2695,1335],{"class":397},[313,2697,2698],{"class":330},"                      CLASS",[313,2700,2701],{"class":330},"   HOSTS",[313,2703,2704],{"class":330},"                             ADDRESS",[313,2706,2707],{"class":330},"   PORTS",[313,2709,2710],{"class":330},"     AGE\n",[313,2712,2713,2716,2719,2722,2725,2728,2731,2734],{"class":315,"line":440},[313,2714,2715],{"class":397},"gitlab-workspaces-proxy",[313,2717,2718],{"class":330},"   nginx",[313,2720,2721],{"class":330},"   remote-dev.dev,",[313,2723,2724],{"class":400},"*",[313,2726,2727],{"class":330},".remote-dev.dev",[313,2729,2730],{"class":330},"             80,",[313,2732,2733],{"class":400}," 443",[313,2735,2736],{"class":330},"   9s\n",[259,2738,107],{"id":2739},"agent-for-kubernetes-installation",[12,2741,2742,2743,2746,2747,2750,2751,2754,2755,2758,2759,2764,2765,2768],{},"Create the agent configuration file in ",[182,2744,2745],{},".gitlab/agents/\u003Cagentname>/config.yaml",", add to git, and push it into the repository. The ",[182,2748,2749],{},"remote_development"," key specifies the ",[182,2752,2753],{},"dns_zone",", which must be set to the workspaces domain. Additionally, the integration needs to be enabled. The ",[182,2756,2757],{},"observability"," key intentionally configures ",[28,2760,2763],{"href":2761,"rel":2762},"https://docs.gitlab.com/user/clusters/agent/work_with_agent/#debug-the-agent",[],"debug logging"," for the first setup to troubleshoot faster. You can adjust the ",[182,2766,2767],{},"logging"," levels for production usage.",[304,2770,2772],{"className":306,"code":2771,"language":308,"meta":309,"style":309},"export GL_AGENT_K8S=remote-dev-dev\n\n$ mkdir agent-kubernetes && cd agent-kubernetes\n$ mkdir -p .gitlab/agents/${GL_AGENT_K8S}/\n\n$ cat \u003C\u003CEOF >.gitlab/agents/${GL_AGENT_K8S}/config.yaml\nremote_development:\n    enabled: true\n    dns_zone: \"${GITLAB_WORKSPACES_PROXY_DOMAIN}\"\n\nobservability:\n  logging:\n    level: debug\n    grpc_level: warn\nEOF\n\n$ git add .gitlab/agents/${GL_AGENT_K8S}/config.yaml\n$ git commit -avm \"Add agent for Kubernetes configuration\"\n# adjust the URL to your GitLab server URL and project path\n$ git remote add origin https://gitlab.example.com/remote-dev-workspaces/agent-kubernetes.git\n# will create a private project when https/PAT is used\n$ git push\n",[182,2773,2774,2786,2790,2809,2827,2831,2855,2860,2865,2875,2879,2884,2889,2894,2899,2904,2908,2923,2938,2943,2960,2965],{"__ignoreMap":309},[313,2775,2776,2778,2781,2783],{"class":315,"line":316},[313,2777,320],{"class":319},[313,2779,2780],{"class":323}," GL_AGENT_K8S",[313,2782,327],{"class":319},[313,2784,2785],{"class":323},"remote-dev-dev\n",[313,2787,2788],{"class":315,"line":334},[313,2789,486],{"emptyLinePlaceholder":485},[313,2791,2792,2794,2797,2800,2803,2806],{"class":315,"line":347},[313,2793,225],{"class":397},[313,2795,2796],{"class":330}," mkdir",[313,2798,2799],{"class":330}," agent-kubernetes",[313,2801,2802],{"class":323}," && ",[313,2804,2805],{"class":400},"cd",[313,2807,2808],{"class":330}," agent-kubernetes\n",[313,2810,2811,2813,2815,2818,2821,2824],{"class":315,"line":440},[313,2812,225],{"class":397},[313,2814,2796],{"class":330},[313,2816,2817],{"class":400}," -p",[313,2819,2820],{"class":330}," .gitlab/agents/",[313,2822,2823],{"class":323},"${GL_AGENT_K8S}",[313,2825,2826],{"class":330},"/\n",[313,2828,2829],{"class":315,"line":451},[313,2830,486],{"emptyLinePlaceholder":485},[313,2832,2833,2835,2838,2841,2844,2847,2850,2852],{"class":315,"line":462},[313,2834,225],{"class":397},[313,2836,2837],{"class":330}," cat",[313,2839,2840],{"class":319}," \u003C\u003C",[313,2842,2843],{"class":330},"EOF",[313,2845,2846],{"class":319}," >",[313,2848,2849],{"class":330},".gitlab/agents/",[313,2851,2823],{"class":323},[313,2853,2854],{"class":397},"/config.yaml\n",[313,2856,2857],{"class":315,"line":470},[313,2858,2859],{"class":330},"remote_development:\n",[313,2861,2862],{"class":315,"line":482},[313,2863,2864],{"class":330},"    enabled: true\n",[313,2866,2867,2870,2872],{"class":315,"line":489},[313,2868,2869],{"class":330},"    dns_zone: \"${",[313,2871,407],{"class":323},[313,2873,2874],{"class":330},"}\"\n",[313,2876,2877],{"class":315,"line":506},[313,2878,486],{"emptyLinePlaceholder":485},[313,2880,2881],{"class":315,"line":519},[313,2882,2883],{"class":330},"observability:\n",[313,2885,2886],{"class":315,"line":528},[313,2887,2888],{"class":330},"  logging:\n",[313,2890,2891],{"class":315,"line":537},[313,2892,2893],{"class":330},"    level: debug\n",[313,2895,2896],{"class":315,"line":546},[313,2897,2898],{"class":330},"    grpc_level: warn\n",[313,2900,2901],{"class":315,"line":553},[313,2902,2903],{"class":330},"EOF\n",[313,2905,2906],{"class":315,"line":1113},[313,2907,486],{"emptyLinePlaceholder":485},[313,2909,2910,2912,2915,2917,2919,2921],{"class":315,"line":1359},[313,2911,225],{"class":397},[313,2913,2914],{"class":330}," git",[313,2916,2081],{"class":330},[313,2918,2820],{"class":330},[313,2920,2823],{"class":323},[313,2922,2854],{"class":330},[313,2924,2925,2927,2929,2932,2935],{"class":315,"line":1386},[313,2926,225],{"class":397},[313,2928,2914],{"class":330},[313,2930,2931],{"class":330}," commit",[313,2933,2934],{"class":400}," -avm",[313,2936,2937],{"class":330}," \"Add agent for Kubernetes configuration\"\n",[313,2939,2940],{"class":315,"line":1391},[313,2941,2942],{"class":839},"# adjust the URL to your GitLab server URL and project path\n",[313,2944,2945,2947,2949,2952,2954,2957],{"class":315,"line":1415},[313,2946,225],{"class":397},[313,2948,2914],{"class":330},[313,2950,2951],{"class":330}," remote",[313,2953,2081],{"class":330},[313,2955,2956],{"class":330}," origin",[313,2958,2959],{"class":330}," https://gitlab.example.com/remote-dev-workspaces/agent-kubernetes.git\n",[313,2961,2962],{"class":315,"line":1435},[313,2963,2964],{"class":839},"# will create a private project when https/PAT is used\n",[313,2966,2968,2970,2972],{"class":315,"line":2967},22,[313,2969,225],{"class":397},[313,2971,2914],{"class":330},[313,2973,2974],{"class":330}," push\n",[12,2976,2977,2978,2981,2982,2985,2986,2989,2990,2993],{},"Open the GitLab project in your browser, navigate into ",[182,2979,2980],{},"Operate > Kubernetes Clusters",", and click the ",[182,2983,2984],{},"Connect a new cluster (agent)"," button. Select the agent from the configuration dropdown, and click ",[182,2987,2988],{},"Register",". The form generates a ready-to-use Helm chart CLI command. Similar to the command below, replace ",[182,2991,2992],{},"XXXXXXXXXXREPLACEME"," with the actual token value.",[304,2995,2997],{"className":306,"code":2996,"language":308,"meta":309,"style":309},"helm repo add gitlab https://charts.gitlab.io\nhelm repo update\nhelm upgrade --install remote-dev-dev gitlab/gitlab-agent \\\n    --namespace gitlab-agent-remote-dev-dev \\\n    --create-namespace \\\n    --set image.tag=v16.0.1 \\\n    --set config.token=XXXXXXXXXXREPLACEME \\\n    --set config.kasAddress=wss://kas.gitlab.com # Replace with your self-managed GitLab KAS instance URL if not using GitLab.com SaaS\n",[182,2998,2999,3013,3021,3037,3047,3054,3064,3073],{"__ignoreMap":309},[313,3000,3001,3003,3005,3007,3010],{"class":315,"line":316},[313,3002,247],{"class":397},[313,3004,2078],{"class":330},[313,3006,2081],{"class":330},[313,3008,3009],{"class":330}," gitlab",[313,3011,3012],{"class":330}," https://charts.gitlab.io\n",[313,3014,3015,3017,3019],{"class":315,"line":334},[313,3016,247],{"class":397},[313,3018,2078],{"class":330},[313,3020,2096],{"class":330},[313,3022,3023,3025,3027,3029,3032,3035],{"class":315,"line":347},[313,3024,247],{"class":397},[313,3026,2287],{"class":330},[313,3028,2290],{"class":400},[313,3030,3031],{"class":330}," remote-dev-dev",[313,3033,3034],{"class":330}," gitlab/gitlab-agent",[313,3036,413],{"class":400},[313,3038,3039,3042,3045],{"class":315,"line":440},[313,3040,3041],{"class":400},"    --namespace",[313,3043,3044],{"class":330}," gitlab-agent-remote-dev-dev",[313,3046,413],{"class":400},[313,3048,3049,3052],{"class":315,"line":451},[313,3050,3051],{"class":400},"    --create-namespace",[313,3053,413],{"class":400},[313,3055,3056,3059,3062],{"class":315,"line":462},[313,3057,3058],{"class":400},"    --set",[313,3060,3061],{"class":330}," image.tag=v16.0.1",[313,3063,413],{"class":400},[313,3065,3066,3068,3071],{"class":315,"line":470},[313,3067,3058],{"class":400},[313,3069,3070],{"class":330}," config.token=XXXXXXXXXXREPLACEME",[313,3072,413],{"class":400},[313,3074,3075,3077,3080],{"class":315,"line":482},[313,3076,3058],{"class":400},[313,3078,3079],{"class":330}," config.kasAddress=wss://kas.gitlab.com",[313,3081,3082],{"class":839}," # Replace with your self-managed GitLab KAS instance URL if not using GitLab.com SaaS\n",[12,3084,3085,3086,3088],{},"Run the commands, and verify that the agent is connected in the ",[182,3087,2980],{}," overview. You can access the pod logs using the following command:",[304,3090,3092],{"className":306,"code":3091,"language":308,"meta":309,"style":309},"$ kubectl get ns\nNAME                          STATUS   AGE\ngitlab-agent-remote-dev-dev   Active   9d\ngitlab-workspaces             Active   22d\n...\n\n$ kubectl logs -f -l app.kubernetes.io/name=gitlab-agent -n gitlab-agent-$GL_AGENT_K8S\n",[182,3093,3094,3106,3116,3127,3138,3142,3146],{"__ignoreMap":309},[313,3095,3096,3098,3101,3103],{"class":315,"line":316},[313,3097,225],{"class":397},[313,3099,3100],{"class":330}," kubectl",[313,3102,1598],{"class":330},[313,3104,3105],{"class":330}," ns\n",[313,3107,3108,3110,3113],{"class":315,"line":334},[313,3109,1335],{"class":397},[313,3111,3112],{"class":330},"                          STATUS",[313,3114,3115],{"class":330},"   AGE\n",[313,3117,3118,3121,3124],{"class":315,"line":347},[313,3119,3120],{"class":397},"gitlab-agent-remote-dev-dev",[313,3122,3123],{"class":330},"   Active",[313,3125,3126],{"class":330},"   9d\n",[313,3128,3129,3132,3135],{"class":315,"line":440},[313,3130,3131],{"class":397},"gitlab-workspaces",[313,3133,3134],{"class":330},"             Active",[313,3136,3137],{"class":330},"   22d\n",[313,3139,3140],{"class":315,"line":451},[313,3141,600],{"class":400},[313,3143,3144],{"class":315,"line":462},[313,3145,486],{"emptyLinePlaceholder":485},[313,3147,3148,3150,3152,3155,3157,3160,3163,3165,3168],{"class":315,"line":470},[313,3149,225],{"class":397},[313,3151,3100],{"class":330},[313,3153,3154],{"class":330}," logs",[313,3156,1561],{"class":400},[313,3158,3159],{"class":400}," -l",[313,3161,3162],{"class":330}," app.kubernetes.io/name=gitlab-agent",[313,3164,2221],{"class":400},[313,3166,3167],{"class":330}," gitlab-agent-",[313,3169,3170],{"class":323},"$GL_AGENT_K8S\n",[12,3172,3173],{},[759,3174,3175],{},"Congrats! Your infrastructure setup for on-demand, cloud-based development environments is complete.",[161,3177,113],{"id":3178},"workspaces-creation",[12,3180,3181,3182,3189,3190,3192,3193,931],{},"After completing the infrastructure setup, you must verify that all components work together and users can create workspaces. You can fork or import the ",[28,3183,3186,1248],{"href":3184,"rel":3185},"https://gitlab.com/gitlab-da/use-cases/remote-development/example-python-http-simple",[],[182,3187,3188],{},"example-python-http-simple"," into your GitLab group with access to the GitLab agent for Kubernetes to try it immediately. The project provides a simple Python web app with Flask that provides different HTTP routes. Alternatively, start with a new project and create a ",[182,3191,184],{}," with the ",[28,3194,3197],{"href":3195,"rel":3196},"https://docs.gitlab.com/user/workspace/#example-configurations",[],"example configuration",[12,3199,3200,3201,3206,3207,3209],{},"Optional: Inspect the ",[28,3202,3204],{"href":177,"rel":3203},[],[182,3205,184],{}," file to learn about the configuration format. We will look into the ",[182,3208,188],{}," key later.",[304,3211,3215],{"className":3212,"code":3213,"language":3214,"meta":309,"style":309},"language-yaml shiki shiki-themes github-light","schemaVersion: 2.2.0\ncomponents:\n  - name: py\n    attributes:\n      gl/inject-editor: true\n    container:\n      # Use a custom image that supports arbitrary user IDs.\n      # NOTE: THIS IMAGE IS NOT ACTIVELY MAINTAINED. DEMO USE CASES ONLY, DO NOT USE IN PRODUCTION.\n      # Source: https://gitlab.com/gitlab-da/use-cases/remote-development/container-images/python-remote-dev-workspaces-user-id\n      image: registry.gitlab.com/gitlab-da/use-cases/remote-development/container-images/python-remote-dev-workspaces-user-id:latest\n      memoryRequest: 1024M\n      memoryLimit: 2048M\n      cpuRequest: 500m\n      cpuLimit: 1000m\n      endpoints:\n        - name: http-python\n          targetPort: 8080\n","yaml",[182,3216,3217,3229,3237,3250,3257,3267,3274,3279,3284,3289,3299,3309,3319,3329,3339,3346,3358],{"__ignoreMap":309},[313,3218,3219,3223,3226],{"class":315,"line":316},[313,3220,3222],{"class":3221},"shJU0","schemaVersion",[313,3224,3225],{"class":323},": ",[313,3227,3228],{"class":400},"2.2.0\n",[313,3230,3231,3234],{"class":315,"line":334},[313,3232,3233],{"class":3221},"components",[313,3235,3236],{"class":323},":\n",[313,3238,3239,3242,3245,3247],{"class":315,"line":347},[313,3240,3241],{"class":323},"  - ",[313,3243,3244],{"class":3221},"name",[313,3246,3225],{"class":323},[313,3248,3249],{"class":330},"py\n",[313,3251,3252,3255],{"class":315,"line":440},[313,3253,3254],{"class":3221},"    attributes",[313,3256,3236],{"class":323},[313,3258,3259,3262,3264],{"class":315,"line":451},[313,3260,3261],{"class":3221},"      gl/inject-editor",[313,3263,3225],{"class":323},[313,3265,3266],{"class":400},"true\n",[313,3268,3269,3272],{"class":315,"line":462},[313,3270,3271],{"class":3221},"    container",[313,3273,3236],{"class":323},[313,3275,3276],{"class":315,"line":470},[313,3277,3278],{"class":839},"      # Use a custom image that supports arbitrary user IDs.\n",[313,3280,3281],{"class":315,"line":482},[313,3282,3283],{"class":839},"      # NOTE: THIS IMAGE IS NOT ACTIVELY MAINTAINED. DEMO USE CASES ONLY, DO NOT USE IN PRODUCTION.\n",[313,3285,3286],{"class":315,"line":489},[313,3287,3288],{"class":839},"      # Source: https://gitlab.com/gitlab-da/use-cases/remote-development/container-images/python-remote-dev-workspaces-user-id\n",[313,3290,3291,3294,3296],{"class":315,"line":506},[313,3292,3293],{"class":3221},"      image",[313,3295,3225],{"class":323},[313,3297,3298],{"class":330},"registry.gitlab.com/gitlab-da/use-cases/remote-development/container-images/python-remote-dev-workspaces-user-id:latest\n",[313,3300,3301,3304,3306],{"class":315,"line":519},[313,3302,3303],{"class":3221},"      memoryRequest",[313,3305,3225],{"class":323},[313,3307,3308],{"class":330},"1024M\n",[313,3310,3311,3314,3316],{"class":315,"line":528},[313,3312,3313],{"class":3221},"      memoryLimit",[313,3315,3225],{"class":323},[313,3317,3318],{"class":330},"2048M\n",[313,3320,3321,3324,3326],{"class":315,"line":537},[313,3322,3323],{"class":3221},"      cpuRequest",[313,3325,3225],{"class":323},[313,3327,3328],{"class":330},"500m\n",[313,3330,3331,3334,3336],{"class":315,"line":546},[313,3332,3333],{"class":3221},"      cpuLimit",[313,3335,3225],{"class":323},[313,3337,3338],{"class":330},"1000m\n",[313,3340,3341,3344],{"class":315,"line":553},[313,3342,3343],{"class":3221},"      endpoints",[313,3345,3236],{"class":323},[313,3347,3348,3351,3353,3355],{"class":315,"line":1113},[313,3349,3350],{"class":323},"        - ",[313,3352,3244],{"class":3221},[313,3354,3225],{"class":323},[313,3356,3357],{"class":330},"http-python\n",[313,3359,3360,3363,3365],{"class":315,"line":1359},[313,3361,3362],{"class":3221},"          targetPort",[313,3364,3225],{"class":323},[313,3366,3367],{"class":400},"8080\n",[259,3369,121],{"id":3370},"create-the-first-workspaces",[12,3372,3373,3374,3377],{},"Navigate to the ",[182,3375,3376],{},"Your Work > Workspaces"," menu and create a new workspace. Search for the project name, select the agent for Kubernetes, and create the workspace.",[12,3379,3380],{},[296,3381],{"alt":3382,"src":3383},"GitLab remote development workspaces, Python example","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398574/blog/Content%20Images/infrastructure-cloud-development-environments/gitlab_remote_dev_workspaces_python.png",[12,3385,3386],{},"Open two terminals to follow the workspaces proxy and agent logs in the Kubernetes cluster.",[304,3388,3390],{"className":306,"code":3389,"language":308,"meta":309,"style":309},"$ kubectl logs -f -l app.kubernetes.io/name=gitlab-workspaces-proxy -n gitlab-workspaces\n\n{\"level\":\"info\",\"ts\":1686331102.886607,\"caller\":\"server/server.go:74\",\"msg\":\"Starting proxy server...\"}\n{\"level\":\"info\",\"ts\":1686331133.146862,\"caller\":\"upstream/tracker.go:47\",\"msg\":\"New upstream added\",\"host\":\"8080-workspace-62029-5534214-2vxdxq.remote-dev.dev\",\"backend\":\"workspace-62029-5534214-2vxdxq.gl-rd-ns-62029-5534214-2vxdxq\",\"backend_port\":8080}\n2023/06/09 17:21:10 getHostnameFromState state=https://60001-workspace-62029-5534214-2vxdxq.remote-dev.dev/folder=/projects/demo-python-http-simple\n",[182,3391,3392,3411,3415,3459,3520],{"__ignoreMap":309},[313,3393,3394,3396,3398,3400,3402,3404,3407,3409],{"class":315,"line":316},[313,3395,225],{"class":397},[313,3397,3100],{"class":330},[313,3399,3154],{"class":330},[313,3401,1561],{"class":400},[313,3403,3159],{"class":400},[313,3405,3406],{"class":330}," app.kubernetes.io/name=gitlab-workspaces-proxy",[313,3408,2221],{"class":400},[313,3410,2687],{"class":330},[313,3412,3413],{"class":315,"line":334},[313,3414,486],{"emptyLinePlaceholder":485},[313,3416,3417,3420,3423,3426,3429,3432,3435,3438,3441,3443,3446,3448,3451,3453,3456],{"class":315,"line":347},[313,3418,3419],{"class":323},"{",[313,3421,3422],{"class":397},"\"level\"",[313,3424,3425],{"class":400},":",[313,3427,3428],{"class":397},"\"info\"",[313,3430,3431],{"class":397},",",[313,3433,3434],{"class":397},"\"ts\"",[313,3436,3437],{"class":397},":1686331102.886607,",[313,3439,3440],{"class":397},"\"caller\"",[313,3442,3425],{"class":400},[313,3444,3445],{"class":397},"\"server/server.go:74\"",[313,3447,3431],{"class":397},[313,3449,3450],{"class":397},"\"msg\"",[313,3452,3425],{"class":400},[313,3454,3455],{"class":397},"\"Starting proxy server...\"",[313,3457,3458],{"class":397},"}\n",[313,3460,3461,3463,3465,3467,3469,3471,3473,3476,3478,3480,3483,3485,3487,3489,3492,3494,3497,3499,3502,3504,3507,3509,3512,3514,3517],{"class":315,"line":440},[313,3462,3419],{"class":323},[313,3464,3422],{"class":397},[313,3466,3425],{"class":400},[313,3468,3428],{"class":397},[313,3470,3431],{"class":397},[313,3472,3434],{"class":397},[313,3474,3475],{"class":397},":1686331133.146862,",[313,3477,3440],{"class":397},[313,3479,3425],{"class":400},[313,3481,3482],{"class":397},"\"upstream/tracker.go:47\"",[313,3484,3431],{"class":397},[313,3486,3450],{"class":397},[313,3488,3425],{"class":400},[313,3490,3491],{"class":397},"\"New upstream added\"",[313,3493,3431],{"class":397},[313,3495,3496],{"class":397},"\"host\"",[313,3498,3425],{"class":400},[313,3500,3501],{"class":397},"\"8080-workspace-62029-5534214-2vxdxq.remote-dev.dev\"",[313,3503,3431],{"class":397},[313,3505,3506],{"class":397},"\"backend\"",[313,3508,3425],{"class":400},[313,3510,3511],{"class":397},"\"workspace-62029-5534214-2vxdxq.gl-rd-ns-62029-5534214-2vxdxq\"",[313,3513,3431],{"class":397},[313,3515,3516],{"class":397},"\"backend_port\"",[313,3518,3519],{"class":397},":8080}\n",[313,3521,3522,3525,3528,3531],{"class":315,"line":451},[313,3523,3524],{"class":397},"2023/06/09",[313,3526,3527],{"class":330}," 17:21:10",[313,3529,3530],{"class":330}," getHostnameFromState",[313,3532,3533],{"class":330}," state=https://60001-workspace-62029-5534214-2vxdxq.remote-dev.dev/folder=/projects/demo-python-http-simple\n",[304,3535,3537],{"className":306,"code":3536,"language":308,"meta":309,"style":309},"$ kubectl logs -f -l app.kubernetes.io/name=gitlab-agent -n gitlab-agent-$GL_AGENT_K8S\n\n{\"level\":\"debug\",\"time\":\"2023-06-09T18:36:19.839Z\",\"msg\":\"Applied event\",\"mod_name\":\"remote_development\",\"apply_event\":\"WaitEvent{ GroupName: \\\"wait-0\\\", Status: \\\"Pending\\\", Identifier: \\\"gl-rd-ns-62029-5534214-k66cjy_workspace-62029-5534214-k66cjy-gl-workspace-data__PersistentVolumeClaim\\\" }\",\"agent_id\":62029}\n{\"level\":\"debug\",\"time\":\"2023-06-09T18:36:19.866Z\",\"msg\":\"Received update event\",\"mod_name\":\"remote_development\",\"workspace_namespace\":\"gl-rd-ns-62029-5534214-k66cjy\",\"workspace_name\":\"workspace-62029-5534214-k66cjy\",\"agent_id\":62029}\n{\"level\":\"debug\",\"time\":\"2023-06-09T18:36:43.627Z\",\"msg\":\"Applied event\",\"mod_name\":\"remote_development\",\"apply_event\":\"WaitEvent{ GroupName: \\\"wait-0\\\", Status: \\\"Successful\\\", Identifier: \\\"gl-rd-ns-62029-5534214-k66cjy_workspace-62029-5534214-k66cjy_apps_Deployment\\\" }\",\"agent_id\":62029}\n",[182,3538,3539,3559,3563,3652,3714],{"__ignoreMap":309},[313,3540,3541,3543,3545,3547,3549,3551,3553,3555,3557],{"class":315,"line":316},[313,3542,225],{"class":397},[313,3544,3100],{"class":330},[313,3546,3154],{"class":330},[313,3548,1561],{"class":400},[313,3550,3159],{"class":400},[313,3552,3162],{"class":330},[313,3554,2221],{"class":400},[313,3556,3167],{"class":330},[313,3558,3170],{"class":323},[313,3560,3561],{"class":315,"line":334},[313,3562,486],{"emptyLinePlaceholder":485},[313,3564,3565,3567,3569,3571,3574,3576,3579,3581,3584,3586,3588,3590,3593,3595,3598,3600,3603,3605,3608,3610,3613,3616,3619,3621,3624,3626,3629,3631,3634,3636,3639,3641,3644,3646,3649],{"class":315,"line":347},[313,3566,3419],{"class":323},[313,3568,3422],{"class":397},[313,3570,3425],{"class":400},[313,3572,3573],{"class":397},"\"debug\"",[313,3575,3431],{"class":397},[313,3577,3578],{"class":397},"\"time\"",[313,3580,3425],{"class":400},[313,3582,3583],{"class":397},"\"2023-06-09T18:36:19.839Z\"",[313,3585,3431],{"class":397},[313,3587,3450],{"class":397},[313,3589,3425],{"class":400},[313,3591,3592],{"class":397},"\"Applied event\"",[313,3594,3431],{"class":397},[313,3596,3597],{"class":397},"\"mod_name\"",[313,3599,3425],{"class":400},[313,3601,3602],{"class":397},"\"remote_development\"",[313,3604,3431],{"class":397},[313,3606,3607],{"class":397},"\"apply_event\"",[313,3609,3425],{"class":400},[313,3611,3612],{"class":397},"\"WaitEvent{ GroupName: ",[313,3614,3615],{"class":400},"\\\"",[313,3617,3618],{"class":397},"wait-0",[313,3620,3615],{"class":400},[313,3622,3623],{"class":397},", Status: ",[313,3625,3615],{"class":400},[313,3627,3628],{"class":397},"Pending",[313,3630,3615],{"class":400},[313,3632,3633],{"class":397},", Identifier: ",[313,3635,3615],{"class":400},[313,3637,3638],{"class":397},"gl-rd-ns-62029-5534214-k66cjy_workspace-62029-5534214-k66cjy-gl-workspace-data__PersistentVolumeClaim",[313,3640,3615],{"class":400},[313,3642,3643],{"class":397}," }\"",[313,3645,3431],{"class":397},[313,3647,3648],{"class":397},"\"agent_id\"",[313,3650,3651],{"class":397},":62029}\n",[313,3653,3654,3656,3658,3660,3662,3664,3666,3668,3671,3673,3675,3677,3680,3682,3684,3686,3688,3690,3693,3695,3698,3700,3703,3705,3708,3710,3712],{"class":315,"line":440},[313,3655,3419],{"class":323},[313,3657,3422],{"class":397},[313,3659,3425],{"class":400},[313,3661,3573],{"class":397},[313,3663,3431],{"class":397},[313,3665,3578],{"class":397},[313,3667,3425],{"class":400},[313,3669,3670],{"class":397},"\"2023-06-09T18:36:19.866Z\"",[313,3672,3431],{"class":397},[313,3674,3450],{"class":397},[313,3676,3425],{"class":400},[313,3678,3679],{"class":397},"\"Received update event\"",[313,3681,3431],{"class":397},[313,3683,3597],{"class":397},[313,3685,3425],{"class":400},[313,3687,3602],{"class":397},[313,3689,3431],{"class":397},[313,3691,3692],{"class":397},"\"workspace_namespace\"",[313,3694,3425],{"class":400},[313,3696,3697],{"class":397},"\"gl-rd-ns-62029-5534214-k66cjy\"",[313,3699,3431],{"class":397},[313,3701,3702],{"class":397},"\"workspace_name\"",[313,3704,3425],{"class":400},[313,3706,3707],{"class":397},"\"workspace-62029-5534214-k66cjy\"",[313,3709,3431],{"class":397},[313,3711,3648],{"class":397},[313,3713,3651],{"class":397},[313,3715,3716,3718,3720,3722,3724,3726,3728,3730,3733,3735,3737,3739,3741,3743,3745,3747,3749,3751,3753,3755,3757,3759,3761,3763,3765,3767,3770,3772,3774,3776,3779,3781,3783,3785,3787],{"class":315,"line":451},[313,3717,3419],{"class":323},[313,3719,3422],{"class":397},[313,3721,3425],{"class":400},[313,3723,3573],{"class":397},[313,3725,3431],{"class":397},[313,3727,3578],{"class":397},[313,3729,3425],{"class":400},[313,3731,3732],{"class":397},"\"2023-06-09T18:36:43.627Z\"",[313,3734,3431],{"class":397},[313,3736,3450],{"class":397},[313,3738,3425],{"class":400},[313,3740,3592],{"class":397},[313,3742,3431],{"class":397},[313,3744,3597],{"class":397},[313,3746,3425],{"class":400},[313,3748,3602],{"class":397},[313,3750,3431],{"class":397},[313,3752,3607],{"class":397},[313,3754,3425],{"class":400},[313,3756,3612],{"class":397},[313,3758,3615],{"class":400},[313,3760,3618],{"class":397},[313,3762,3615],{"class":400},[313,3764,3623],{"class":397},[313,3766,3615],{"class":400},[313,3768,3769],{"class":397},"Successful",[313,3771,3615],{"class":400},[313,3773,3633],{"class":397},[313,3775,3615],{"class":400},[313,3777,3778],{"class":397},"gl-rd-ns-62029-5534214-k66cjy_workspace-62029-5534214-k66cjy_apps_Deployment",[313,3780,3615],{"class":400},[313,3782,3643],{"class":397},[313,3784,3431],{"class":397},[313,3786,3648],{"class":397},[313,3788,3651],{"class":397},[12,3790,3791,3792,3795],{},"Wait until the workspace is provisioned successfully, and click to open the HTTP URL, example format ",[182,3793,3794],{},"https://60001-workspace-62029-5534214-2vxdxq.remote-dev.dev/?folder=%2Fprojects%2Fexample-python-http-simple",". The GitLab OAuth application will ask you for authorization.",[12,3797,3798],{},[296,3799],{"alt":3800,"src":3801},"GitLab OAuth provider app, example with the Developer Evangelism demo environment","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398574/blog/Content%20Images/infrastructure-cloud-development-environments/gitlab_remote_dev_workspaces_oauth_app.png",[12,3803,3804,3805,3808,3809,3812,3813,931],{},"You can select the Web IDE menu, open a new terminal (",[182,3806,3807],{},"cmd shift p"," and search for ",[182,3810,3811],{},"terminal create","). More shortcuts and Web IDE usage are documented ",[28,3814,3817],{"href":3815,"rel":3816},"https://docs.gitlab.com/user/project/web_ide/",[],"here",[12,3819,3820],{},[296,3821],{"alt":3822,"src":3823},"GitLab remote development workspaces, Python example, create terminal","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398577/blog/Content%20Images/infrastructure-cloud-development-environments/gitlab_remote_dev_workspaces_python_web_ide_create_terminal.png",[12,3825,3826,3827,3830,3831,3834,3835,3838,3839,3842],{},"Using the Python example project, try to run the ",[182,3828,3829],{},"hello.py"," file with the Python interpreter after changing the terminal to ",[182,3832,3833],{},"bash"," to access auto-completion and shell history. Type ",[182,3836,3837],{},"pyth",", press tab, type ",[182,3840,3841],{},"hel",", press tab, enter.",[304,3844,3846],{"className":306,"code":3845,"language":308,"meta":309,"style":309},"$ bash\n\n$ python hello.py\n",[182,3847,3848,3855,3859],{"__ignoreMap":309},[313,3849,3850,3852],{"class":315,"line":316},[313,3851,225],{"class":397},[313,3853,3854],{"class":330}," bash\n",[313,3856,3857],{"class":315,"line":334},[313,3858,486],{"emptyLinePlaceholder":485},[313,3860,3861,3863,3866],{"class":315,"line":347},[313,3862,225],{"class":397},[313,3864,3865],{"class":330}," python",[313,3867,3868],{"class":330}," hello.py\n",[12,3870,3871],{},"The command will fail because the Python requirements still need to be installed. Let us fix that by running the following command:",[304,3873,3875],{"className":306,"code":3874,"language":308,"meta":309,"style":309},"$ pip install -r requirements.txt\n",[182,3876,3877],{"__ignoreMap":309},[313,3878,3879,3881,3884,3886,3889],{"class":315,"line":316},[313,3880,225],{"class":397},[313,3882,3883],{"class":330}," pip",[313,3885,1156],{"class":330},[313,3887,3888],{"class":400}," -r",[313,3890,3891],{"class":330}," requirements.txt\n",[12,3893,3894],{},[296,3895],{"alt":3896,"src":3897},"GitLab remote development workspaces, Python example, install requirements in the terminal","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398577/blog/Content%20Images/infrastructure-cloud-development-environments/gitlab_remote_dev_workspaces_python_web_ide_terminal_install_pip.png",[12,3899,3900,3902,3903,3906],{},[361,3901,750],{},": This example is intentionally kept simple, and does not use best practices with ",[182,3904,3905],{},"pyenv"," for managing Python environments. We will explore development environment templates in future blog posts.",[12,3908,3909,3910,3912],{},"Run the Python application ",[182,3911,3829],{}," again to start the web server on port 8080.",[304,3914,3916],{"className":306,"code":3915,"language":308,"meta":309,"style":309},"$ python hello.py\n",[182,3917,3918],{"__ignoreMap":309},[313,3919,3920,3922,3924],{"class":315,"line":316},[313,3921,225],{"class":397},[313,3923,3865],{"class":330},[313,3925,3868],{"class":330},[12,3927,3928,3929,3932,3933,3936],{},"You can access the exposed port by modifying the URL from the default port at the beginning of the URL to the exposed port ",[182,3930,3931],{},"8080",". The ",[182,3934,3935],{},"?folder"," URL parameter can also be removed.",[304,3938,3942],{"className":3939,"code":3940,"language":3941,"meta":309,"style":309},"language-diff shiki shiki-themes github-light","-https://60001-workspace-62029-5534214-kbtcmq.remote-dev.dev/?folder=/projects/example-python-http-simple\n+https://8080-workspace-62029-5534214-kbtcmq.remote-dev.dev/\n","diff",[182,3943,3944,3950],{"__ignoreMap":309},[313,3945,3946],{"class":315,"line":316},[313,3947,3949],{"class":3948},"s9hmT","-https://60001-workspace-62029-5534214-kbtcmq.remote-dev.dev/?folder=/projects/example-python-http-simple\n",[313,3951,3952],{"class":315,"line":334},[313,3953,3954],{"class":3221},"+https://8080-workspace-62029-5534214-kbtcmq.remote-dev.dev/\n",[12,3956,3957],{},"The URL is not publicly available and requires access through the GitLab OAuth session.",[12,3959,3960],{},[296,3961],{"alt":3962,"src":3963},"GitLab remote development workspaces, Python example, run webserver, access HTTP","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398577/blog/Content%20Images/infrastructure-cloud-development-environments/gitlab_remote_dev_workspaces_python_web_ide_terminal_run_webserver_access_http.png",[12,3965,3966,3967,3972],{},"Modifying the workspace requires custom container images supporting to run with ",[28,3968,3971],{"href":3969,"rel":3970},"https://docs.gitlab.com/user/workspace/#arbitrary-user-ids",[],"arbitrary user IDs",". The example project uses a custom image which allows to install Python dependencies and create build artifacts. It also allows to use the bash terminal shown above. Learn more about custom image creation in the next section.",[259,3974,127],{"id":3975},"custom-workspace-container-images",[12,3977,3978,3979,3982,3983,3987,3988,3993],{},"Custom container images require support for ",[28,3980,3971],{"href":3969,"rel":3981},[],". You can build custom container images with ",[28,3984,3986],{"href":3985},"/solutions/continuous-integration/","GitLab CI/CD"," and use the ",[28,3989,3992],{"href":3990,"rel":3991},"https://docs.gitlab.com/user/packages/container_registry/",[],"GitLab container registry"," to distribute the container images on the DevSecOps platform.",[12,3995,3996,3997,4002,4003,4006,4007,4010,4011,4014,4015,4018],{},"Workspaces run with arbitrary user IDs in the Kubernetes cluster containers and manage resource access with Linux group permissions. Existing container images may need to be changed, and imported as base image for new container images. The ",[28,3998,4001],{"href":3999,"rel":4000},"https://gitlab.com/gitlab-da/use-cases/remote-development/container-images/python-remote-dev-workspaces-user-id/-/blob/main/Dockerfile",[],"following example"," uses the ",[182,4004,4005],{},"python:3.11-slim-bullseye"," image from Docker Hub as a base container image in the ",[182,4008,4009],{},"FROM"," key. The next steps create and set a home directory in ",[182,4012,4013],{},"/home/gitlab-workspaces",", and manage user and group access to specified directories. Additionally, you can install more convenience tools and configurations into the image, for example the ",[182,4016,4017],{},"git"," package.",[12,4020,4021],{},[28,4022,4024],{"href":3999,"rel":4023},[],[182,4025,4026],{},"Dockerfile",[304,4028,4031],{"className":4029,"code":4030,"language":568,"meta":309},[566],"# Example demo for a Python-based container image.\n# NOTE: THIS IMAGE IS NOT ACTIVELY MAINTAINED. DEMO USE CASES ONLY, DO NOT USE IN PRODUCTION.\n\nFROM python:3.11-slim-bullseye\n\n# User id for build time. Runtime will be an arbitrary random ID.\nRUN useradd -l -u 33333 -G sudo -md /home/gitlab-workspaces -s /bin/bash -p gitlab-workspaces gitlab-workspaces\n\nENV HOME=/home/gitlab-workspaces\n\nWORKDIR $HOME\n\nRUN mkdir -p /home/gitlab-workspaces && chgrp -R 0 /home && chmod -R g=u /etc/passwd /etc/group /home\n\n# TODO: Add more convenience tools into the user home directory, i.e. enable color prompt for the terminal, install pyenv to manage Python environments, etc\nRUN apt update && \\\n    apt -y --no-install-recommends install git procps findutils htop vim curl wget && \\\n    rm -rf /var/lib/apt/lists/*\n\nUSER gitlab-workspaces\n",[182,4032,4030],{"__ignoreMap":309},[12,4034,4035,1749,4038,4043,4044,4046,4047,4050,4051,4053],{},[361,4036,4037],{},"As an exercise",[28,4039,4042],{"href":4040,"rel":4041},"https://gitlab.com/gitlab-da/use-cases/remote-development/container-images/python-remote-dev-workspaces-user-id",[],"fork the project"," and modify the package installation step in the ",[182,4045,4026],{}," file to install the ",[182,4048,4049],{},"dnsutils"," package on the Debian based image to get access to the ",[182,4052,576],{}," command.",[12,4055,4056],{},[28,4057,4059],{"href":3999,"rel":4058},[],[182,4060,4026],{},[304,4062,4064],{"className":3939,"code":4063,"language":3941,"meta":309,"style":309},"-RUN apt update && \\\n-    apt -y --no-install-recommends install git procps findutils htop vim curl wget && \\\n-    rm -rf /var/lib/apt/lists/*\n+RUN apt update && \\\n+    apt -y --no-install-recommends install git procps findutils htop vim curl wget dnsutils && \\\n+    rm -rf /var/lib/apt/lists/*\n",[182,4065,4066,4071,4076,4081,4086,4091],{"__ignoreMap":309},[313,4067,4068],{"class":315,"line":316},[313,4069,4070],{"class":3948},"-RUN apt update && \\\n",[313,4072,4073],{"class":315,"line":334},[313,4074,4075],{"class":3948},"-    apt -y --no-install-recommends install git procps findutils htop vim curl wget && \\\n",[313,4077,4078],{"class":315,"line":347},[313,4079,4080],{"class":3948},"-    rm -rf /var/lib/apt/lists/*\n",[313,4082,4083],{"class":315,"line":440},[313,4084,4085],{"class":3221},"+RUN apt update && \\\n",[313,4087,4088],{"class":315,"line":451},[313,4089,4090],{"class":3221},"+    apt -y --no-install-recommends install git procps findutils htop vim curl wget dnsutils && \\\n",[313,4092,4093],{"class":315,"line":462},[313,4094,4095],{"class":3221},"+    rm -rf /var/lib/apt/lists/*\n",[12,4097,4098,4103,4104,4107],{},[28,4099,4102],{"href":4100,"rel":4101},"https://docs.gitlab.com/ci/docker/using_docker_build/",[],"Build the container image"," with your preferred CI/CD workflow. On GitLab.com SaaS, you can include the ",[182,4105,4106],{},"Docker.gitlab-ci.yml"," template which takes care of building the image.",[304,4109,4111],{"className":3212,"code":4110,"language":3214,"meta":309,"style":309},"include:\n    - template: Docker.gitlab-ci.yml\n",[182,4112,4113,4120],{"__ignoreMap":309},[313,4114,4115,4118],{"class":315,"line":316},[313,4116,4117],{"class":3221},"include",[313,4119,3236],{"class":323},[313,4121,4122,4125,4128,4130],{"class":315,"line":334},[313,4123,4124],{"class":323},"    - ",[313,4126,4127],{"class":3221},"template",[313,4129,3225],{"class":323},[313,4131,4132],{"class":330},"Docker.gitlab-ci.yml\n",[12,4134,4135,4136,925,4138,4141,4142,4147],{},"When building the container images manually, use Linux and ",[182,4137,924],{},[28,4139,930],{"href":928,"rel":4140},[],". Also, review the ",[28,4143,4146],{"href":4144,"rel":4145},"https://docs.gitlab.com/ci/pipelines/pipeline_efficiency/#optimize-docker-images",[],"optimizing images guide in the documentation"," when creating custom container images to optimize size and build times.",[12,4149,4150,4151,4154,4155,4157,4158,4160,4161,4163],{},"Navigate into ",[182,4152,4153],{},"Deploy > Container Registry"," in the GitLab UI and copy the image URL from the tagged image. Open the ",[182,4156,184],{}," file in the forked GitLab project ",[182,4159,3188],{},", and change the ",[182,4162,188],{}," path to the newly built image URL.",[12,4165,4166],{},[28,4167,4170],{"href":4168,"rel":4169},"https://gitlab.com/gitlab-da/use-cases/remote-development/example-python-http-simple/-/blob/main/.devfile.yaml",[],[182,4171,184],{},[304,4173,4175],{"className":3939,"code":4174,"language":3941,"meta":309,"style":309},"-      image: registry.gitlab.com/gitlab-da/use-cases/remote-development/container-images/python-remote-dev-workspaces-user-id:latest\n+      image: registry.gitlab.example.com/remote-dev-workspaces/python-remote-dev-workspaces-user-id:latest\n",[182,4176,4177,4182],{"__ignoreMap":309},[313,4178,4179],{"class":315,"line":316},[313,4180,4181],{"class":3948},"-      image: registry.gitlab.com/gitlab-da/use-cases/remote-development/container-images/python-remote-dev-workspaces-user-id:latest\n",[313,4183,4184],{"class":315,"line":334},[313,4185,4186],{"class":3221},"+      image: registry.gitlab.example.com/remote-dev-workspaces/python-remote-dev-workspaces-user-id:latest\n",[12,4188,4150,4189,4191,4192,4194],{},[182,4190,3376],{}," and create a new workspace for the project, and try to execute the ",[182,4193,576],{}," command to query the IPv6 address of GitLab.com (or any other internal domain).",[304,4196,4198],{"className":306,"code":4197,"language":308,"meta":309,"style":309},"$ dig +short gitlab.com AAAA\n",[182,4199,4200],{"__ignoreMap":309},[313,4201,4202,4204,4206,4209,4212],{"class":315,"line":316},[313,4203,225],{"class":397},[313,4205,589],{"class":330},[313,4207,4208],{"class":330}," +short",[313,4210,4211],{"class":330}," gitlab.com",[313,4213,4214],{"class":330}," AAAA\n",[12,4216,4217,4218,931],{},"The custom container image project is located ",[28,4219,3817],{"href":4220,"rel":4221},"https://gitlab.com/gitlab-da/use-cases/remote-development/container-images/python-remote-dev-workspaces-user-id/",[],[161,4223,133],{"id":4224},"tips",[12,4226,4227],{},"This blog post's setup steps with environment variables are easy to follow. For production usage, use automation to manage your environment with Terraform, Ansible, etc.",[22,4229,4230,4253],{},[25,4231,4232,4233,1749,4238,1749,4243,1749,4248],{},"Terraform: ",[28,4234,4237],{"href":4235,"rel":4236},"https://developer.hashicorp.com/terraform/tutorials/kubernetes/gke",[],"Provision a GKE Cluster (Google Cloud)",[28,4239,4242],{"href":4240,"rel":4241},"https://developer.hashicorp.com/terraform/tutorials/kubernetes/eks",[],"Provision an EKS Cluster (AWS)",[28,4244,4247],{"href":4245,"rel":4246},"https://developer.hashicorp.com/terraform/tutorials/kubernetes/aks",[],"Provision an AKS Cluster (Azure)",[28,4249,4252],{"href":4250,"rel":4251},"https://developer.hashicorp.com/terraform/tutorials/kubernetes/helm-provider",[],"Deploy Applications with the Helm Provider",[25,4254,4255,4256,1749,4261,1749,4266,1749,4271],{},"Ansible: ",[28,4257,4260],{"href":4258,"rel":4259},"https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_container_cluster_module.html",[],"google.cloud.gcp_container_cluster module",[28,4262,4265],{"href":4263,"rel":4264},"https://docs.ansible.com/ansible/latest/collections/community/aws/eks_cluster_module.html",[],"community.aws.eks_cluster module",[28,4267,4270],{"href":4268,"rel":4269},"https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_aks_module.html",[],"azure.azcollection.azure_rm_aks module",[28,4272,4275],{"href":4273,"rel":4274},"https://docs.ansible.com/ansible/latest/collections/kubernetes/core/index.html#plugin-index",[],"kubernetes.core collection",[259,4277,141],{"id":4278},"certificate-management",[12,4280,4281,4282,4287],{},"The workspaces domain requires a valid TLS certificate. The examples above used certbot with Let's Encrypt, requiring a certificate renewal after three months. Depending on your corporate requirements, you may need to create TLS certificates signed by the corporate CA identity and manage the certificates. Alternatively, you can look into solutions like ",[28,4283,4286],{"href":4284,"rel":4285},"https://cert-manager.io/docs/getting-started/",[],"cert-manager for Kubernetes"," that will help renew certificates automatically.",[12,4289,4290,4291,4296],{},"Do not forget to add TLS certificate validity monitoring to avoid unforeseen errors. The ",[28,4292,4295],{"href":4293,"rel":4294},"https://github.com/prometheus/blackbox_exporter",[],"blackbox exporter for Prometheus"," can help with monitoring TLS certificate expiry and send alerts.",[259,4298,147],{"id":4299},"troubleshooting",[12,4301,4302],{},"Here are a few tips for troubleshooting connections and inspecting the cluster resources.",[4304,4305,4307],"h4",{"id":4306},"verify-the-connections","Verify the connections",[12,4309,4310],{},"Try to connect to the workspaces domain to see whether the Kubernetes Ingress controller responds to HTTP requests.",[304,4312,4314],{"className":306,"code":4313,"language":308,"meta":309,"style":309},"$ curl -vL ${GITLAB_WORKSPACES_PROXY_DOMAIN}\n",[182,4315,4316],{"__ignoreMap":309},[313,4317,4318,4320,4323,4326],{"class":315,"line":316},[313,4319,225],{"class":397},[313,4321,4322],{"class":330}," curl",[313,4324,4325],{"class":400}," -vL",[313,4327,4328],{"class":323}," ${GITLAB_WORKSPACES_PROXY_DOMAIN}\n",[12,4330,4331],{},"Inspect the logs of the proxy deployment to follow connection requests. Since the proxy requires an authorization token sent via the OAuth app, an HTTP 400 error is expected for unauthenticated curl requests.",[304,4333,4335],{"className":306,"code":4334,"language":308,"meta":309,"style":309},"$ kubectl logs -f -l app.kubernetes.io/name=gitlab-workspaces-proxy -n gitlab-workspaces\n",[182,4336,4337],{"__ignoreMap":309},[313,4338,4339,4341,4343,4345,4347,4349,4351,4353],{"class":315,"line":316},[313,4340,225],{"class":397},[313,4342,3100],{"class":330},[313,4344,3154],{"class":330},[313,4346,1561],{"class":400},[313,4348,3159],{"class":400},[313,4350,3406],{"class":330},[313,4352,2221],{"class":400},[313,4354,2687],{"class":330},[12,4356,4357,4358,4361],{},"Check if the TLS certificate is valid. You can also use ",[182,4359,4360],{},"sslcan"," and other tools.",[304,4363,4365],{"className":306,"code":4364,"language":308,"meta":309,"style":309},"$ openssl s_client -connect ${GITLAB_WORKSPACES_PROXY_DOMAIN}:443\n\n$ sslcan ${GITLAB_WORKSPACES_PROXY_DOMAIN}\n",[182,4366,4367,4386,4390],{"__ignoreMap":309},[313,4368,4369,4371,4374,4377,4380,4383],{"class":315,"line":316},[313,4370,225],{"class":397},[313,4372,4373],{"class":330}," openssl",[313,4375,4376],{"class":330}," s_client",[313,4378,4379],{"class":400}," -connect",[313,4381,4382],{"class":323}," ${GITLAB_WORKSPACES_PROXY_DOMAIN}",[313,4384,4385],{"class":330},":443\n",[313,4387,4388],{"class":315,"line":334},[313,4389,486],{"emptyLinePlaceholder":485},[313,4391,4392,4394,4397],{"class":315,"line":347},[313,4393,225],{"class":397},[313,4395,4396],{"class":330}," sslcan",[313,4398,4328],{"class":323},[12,4400,4401,4405],{},[28,4402,4404],{"href":2761,"rel":4403},[],"Debug the agent for Kubernetes"," and inspect the pod logs.",[304,4407,4409],{"className":306,"code":4408,"language":308,"meta":309,"style":309},"$ kubectl get ns\n\n$ kubectl logs -f -l app.kubernetes.io/name=gitlab-agent -n gitlab-agent-\u003CNAMESPACENAME>\n",[182,4410,4411,4421,4425],{"__ignoreMap":309},[313,4412,4413,4415,4417,4419],{"class":315,"line":316},[313,4414,225],{"class":397},[313,4416,3100],{"class":330},[313,4418,1598],{"class":330},[313,4420,3105],{"class":330},[313,4422,4423],{"class":315,"line":334},[313,4424,486],{"emptyLinePlaceholder":485},[313,4426,4427,4429,4431,4433,4435,4437,4439,4441,4443,4446,4449,4452],{"class":315,"line":347},[313,4428,225],{"class":397},[313,4430,3100],{"class":330},[313,4432,3154],{"class":330},[313,4434,1561],{"class":400},[313,4436,3159],{"class":400},[313,4438,3162],{"class":330},[313,4440,2221],{"class":400},[313,4442,3167],{"class":330},[313,4444,4445],{"class":319},"\u003C",[313,4447,4448],{"class":330},"NAMESPACENAM",[313,4450,4451],{"class":323},"E",[313,4453,4454],{"class":319},">\n",[4304,4456,4458],{"id":4457},"workspaces-cannot-be-created-even-if-the-agent-is-connected","Workspaces cannot be created even if the agent is connected",[12,4460,4461,4462,931],{},"When the workspaces deployment is spinning and nothing happens, try restarting the workspaces proxy and agent for Kubernetes. This is a known problem and tracked ",[28,4463,4466],{"href":4464,"rel":4465},"https://gitlab.com/gitlab-org/gitlab/-/issues/414399#note_1426652421",[],"in this issue",[304,4468,4470],{"className":306,"code":4469,"language":308,"meta":309,"style":309},"$ kubectl rollout restart deployment -n gitlab-workspaces\n\n$ kubectl rollout restart deployment -n gitlab-agent-$GL_AGENT_K8S\n",[182,4471,4472,4491,4495],{"__ignoreMap":309},[313,4473,4474,4476,4478,4481,4484,4487,4489],{"class":315,"line":316},[313,4475,225],{"class":397},[313,4477,3100],{"class":330},[313,4479,4480],{"class":330}," rollout",[313,4482,4483],{"class":330}," restart",[313,4485,4486],{"class":330}," deployment",[313,4488,2221],{"class":400},[313,4490,2687],{"class":330},[313,4492,4493],{"class":315,"line":334},[313,4494,486],{"emptyLinePlaceholder":485},[313,4496,4497,4499,4501,4503,4505,4507,4509,4511],{"class":315,"line":347},[313,4498,225],{"class":397},[313,4500,3100],{"class":330},[313,4502,4480],{"class":330},[313,4504,4483],{"class":330},[313,4506,4486],{"class":330},[313,4508,2221],{"class":400},[313,4510,3167],{"class":330},[313,4512,3170],{"class":323},[12,4514,4515,4516,244,4518,4523],{},"If the agent for Kubernetes remains unresponsive, consider a complete reinstall. First, navigate into the GitLab UI into ",[182,4517,2980],{},[28,4519,4522],{"href":4520,"rel":4521},"https://docs.gitlab.com/user/clusters/agent/work_with_agent/#remove-an-agent-through-the-gitlab-ui",[],"delete the agent",". Next, use the following commands to delete the Helm release from the cluster, and run the installation command generated from the UI again.",[304,4525,4527],{"className":306,"code":4526,"language":308,"meta":309,"style":309},"kubectl get ns\nhelm list -A\n\nexport RELEASENAME=xxx\nexport NAMESPACENAME=xxx\nexport TOKEN=XXXXXXXXXXREPLACEME\nhelm uninstall ${RELEASENAME} -n gitlab-agent-${NAMESPACENAME}\n\nhelm repo add gitlab https://charts.gitlab.io\nhelm repo update\n\nhelm upgrade --install ${RELEASENAME} gitlab/gitlab-agent \\\n    --namespace gitlab-agent-${NAMESPACENAME} \\\n    --create-namespace \\\n    --set image.tag=v16.1.2 \\\n    --set config.token=${TOKEN} \\\n    --set config.kasAddress=wss://kas.gitlab.com # Replace with your self-managed GitLab KAS instance URL if not using GitLab.com SaaS\n",[182,4528,4529,4537,4546,4550,4562,4573,4585,4603,4607,4619,4627,4631,4646,4657,4663,4672,4684],{"__ignoreMap":309},[313,4530,4531,4533,4535],{"class":315,"line":316},[313,4532,243],{"class":397},[313,4534,1598],{"class":330},[313,4536,3105],{"class":330},[313,4538,4539,4541,4544],{"class":315,"line":334},[313,4540,247],{"class":397},[313,4542,4543],{"class":330}," list",[313,4545,1604],{"class":400},[313,4547,4548],{"class":315,"line":347},[313,4549,486],{"emptyLinePlaceholder":485},[313,4551,4552,4554,4557,4559],{"class":315,"line":440},[313,4553,320],{"class":319},[313,4555,4556],{"class":323}," RELEASENAME",[313,4558,327],{"class":319},[313,4560,4561],{"class":323},"xxx\n",[313,4563,4564,4566,4569,4571],{"class":315,"line":451},[313,4565,320],{"class":319},[313,4567,4568],{"class":323}," NAMESPACENAME",[313,4570,327],{"class":319},[313,4572,4561],{"class":323},[313,4574,4575,4577,4580,4582],{"class":315,"line":462},[313,4576,320],{"class":319},[313,4578,4579],{"class":323}," TOKEN",[313,4581,327],{"class":319},[313,4583,4584],{"class":323},"XXXXXXXXXXREPLACEME\n",[313,4586,4587,4589,4592,4595,4598,4600],{"class":315,"line":470},[313,4588,247],{"class":397},[313,4590,4591],{"class":330}," uninstall",[313,4593,4594],{"class":323}," ${RELEASENAME} ",[313,4596,4597],{"class":400},"-n",[313,4599,3167],{"class":330},[313,4601,4602],{"class":323},"${NAMESPACENAME}\n",[313,4604,4605],{"class":315,"line":482},[313,4606,486],{"emptyLinePlaceholder":485},[313,4608,4609,4611,4613,4615,4617],{"class":315,"line":489},[313,4610,247],{"class":397},[313,4612,2078],{"class":330},[313,4614,2081],{"class":330},[313,4616,3009],{"class":330},[313,4618,3012],{"class":330},[313,4620,4621,4623,4625],{"class":315,"line":506},[313,4622,247],{"class":397},[313,4624,2078],{"class":330},[313,4626,2096],{"class":330},[313,4628,4629],{"class":315,"line":519},[313,4630,486],{"emptyLinePlaceholder":485},[313,4632,4633,4635,4637,4639,4641,4644],{"class":315,"line":528},[313,4634,247],{"class":397},[313,4636,2287],{"class":330},[313,4638,2290],{"class":400},[313,4640,4594],{"class":323},[313,4642,4643],{"class":330},"gitlab/gitlab-agent",[313,4645,413],{"class":400},[313,4647,4648,4650,4652,4655],{"class":315,"line":537},[313,4649,3041],{"class":400},[313,4651,3167],{"class":330},[313,4653,4654],{"class":323},"${NAMESPACENAME} ",[313,4656,1279],{"class":400},[313,4658,4659,4661],{"class":315,"line":546},[313,4660,3051],{"class":400},[313,4662,413],{"class":400},[313,4664,4665,4667,4670],{"class":315,"line":553},[313,4666,3058],{"class":400},[313,4668,4669],{"class":330}," image.tag=v16.1.2",[313,4671,413],{"class":400},[313,4673,4674,4676,4679,4682],{"class":315,"line":1113},[313,4675,3058],{"class":400},[313,4677,4678],{"class":330}," config.token=",[313,4680,4681],{"class":323},"${TOKEN} ",[313,4683,1279],{"class":400},[313,4685,4686,4688,4690],{"class":315,"line":1359},[313,4687,3058],{"class":400},[313,4689,3079],{"class":330},[313,4691,3082],{"class":839},[12,4693,4694,4695],{},"Example: ",[182,4696,4697],{},"helm uninstall remote-dev-dev -n gitlab-agent-remote-dev-dev",[4304,4699,4701],{"id":4700},"cannot-modify-workspace-using-custom-images","Cannot modify workspace using custom images",[12,4703,4704],{},"If you cannot modify the workspace, open a new terminal and check the user id and their groups.",[304,4706,4708],{"className":306,"code":4707,"language":308,"meta":309,"style":309},"$ id\n",[182,4709,4710],{"__ignoreMap":309},[313,4711,4712,4714],{"class":315,"line":316},[313,4713,225],{"class":397},[313,4715,4716],{"class":330}," id\n",[12,4718,4719,4720,4722,4723,4725,4726,4729],{},"Inspect the ",[182,4721,184],{}," file in the project and extract the ",[182,4724,188],{}," attribute to test the used container image. You can use container CLI, for example ",[182,4727,4728],{},"docker"," that runs the container with a different user ID. Note: You can use any user ID to test the behavior.",[12,4731,4732,4733,931],{},"Tip: Use grep and cut commands to extract the image attribute URL from the ",[182,4734,184],{},[304,4736,4738],{"className":306,"code":4737,"language":308,"meta":309,"style":309},"$ cat .devfile.yaml | grep image: | cut -f2 -d ':')\n",[182,4739,4740],{"__ignoreMap":309},[313,4741,4742,4744,4746,4749,4752,4755,4758,4760,4763,4766,4768,4771],{"class":315,"line":316},[313,4743,225],{"class":397},[313,4745,2837],{"class":330},[313,4747,4748],{"class":330}," .devfile.yaml",[313,4750,4751],{"class":319}," |",[313,4753,4754],{"class":397}," grep",[313,4756,4757],{"class":330}," image:",[313,4759,4751],{"class":319},[313,4761,4762],{"class":397}," cut",[313,4764,4765],{"class":400}," -f2",[313,4767,401],{"class":400},[313,4769,4770],{"class":330}," ':'",[313,4772,1547],{"class":323},[12,4774,4775,4776,4779],{},"Run the following command to execute the ",[182,4777,4778],{},"id"," command in the container, and print the user information.",[304,4781,4783],{"className":306,"code":4782,"language":308,"meta":309,"style":309},"$ docker run -u 1234 -ti registry.gitlab.com/path/to/project/image:tagname id\n",[182,4784,4785],{"__ignoreMap":309},[313,4786,4787,4789,4792,4795,4798,4801,4804,4807],{"class":315,"line":316},[313,4788,225],{"class":397},[313,4790,4791],{"class":330}," docker",[313,4793,4794],{"class":330}," run",[313,4796,4797],{"class":400}," -u",[313,4799,4800],{"class":400}," 1234",[313,4802,4803],{"class":400}," -ti",[313,4805,4806],{"class":330}," registry.gitlab.com/path/to/project/image:tagname",[313,4808,4716],{"class":330},[12,4810,4811,4812,4815],{},"Try to modify the workspace by running the command ",[182,4813,4814],{},"echo 'Hi' >> ~/example.md",". This can fail with a permission error.",[304,4817,4819],{"className":306,"code":4818,"language":308,"meta":309,"style":309},"$ docker run -u 1234 -ti registry.gitlab.com/path/to/project/image:tagname echo 'Hi' >> ~/example.md\n",[182,4820,4821],{"__ignoreMap":309},[313,4822,4823,4825,4827,4829,4831,4833,4835,4837,4840,4843,4846],{"class":315,"line":316},[313,4824,225],{"class":397},[313,4826,4791],{"class":330},[313,4828,4794],{"class":330},[313,4830,4797],{"class":400},[313,4832,4800],{"class":400},[313,4834,4803],{"class":400},[313,4836,4806],{"class":330},[313,4838,4839],{"class":330}," echo",[313,4841,4842],{"class":330}," 'Hi'",[313,4844,4845],{"class":319}," >>",[313,4847,4848],{"class":330}," ~/example.md\n",[12,4850,4851,4852,4053],{},"If the above command failed, the Linux user group does not have enough permissions to modify the file. You can view the permissions using the ",[182,4853,4854],{},"ls",[304,4856,4858],{"className":306,"code":4857,"language":308,"meta":309,"style":309},"$ docker run -u 1234 -ti registry.gitlab.com/path/to/project/image:tagname ls -lart ~/\n",[182,4859,4860],{"__ignoreMap":309},[313,4861,4862,4864,4866,4868,4870,4872,4874,4876,4879,4882],{"class":315,"line":316},[313,4863,225],{"class":397},[313,4865,4791],{"class":330},[313,4867,4794],{"class":330},[313,4869,4797],{"class":400},[313,4871,4800],{"class":400},[313,4873,4803],{"class":400},[313,4875,4806],{"class":330},[313,4877,4878],{"class":330}," ls",[313,4880,4881],{"class":400}," -lart",[313,4883,4884],{"class":330}," ~/\n",[259,4886,153],{"id":4887},"contribute",[12,4889,4890,4891,4896,4897,4901],{},"The ",[28,4892,4895],{"href":4893,"rel":4894},"https://gitlab.com/gitlab-org/remote-development/gitlab-remote-development-docs",[],"remote development developer documentation"," provides insights into the architecture blueprint and how to set up a local development environment to ",[28,4898,4900],{"href":4899},"/community/contribute/","start contributing",". In the future, we will be able to use remote development workspaces to develop remote development workspaces.",[161,4903,159],{"id":4904},"share-your-feedback",[12,4906,4907],{},"In this blog post, you have learned how to manage the infrastructure for remote development workspaces, create your first workspace, and more tips on custom workspace images and troubleshooting. Using the same development environment across organizations and communities, developers can focus on writing code and get fast preview feedback (i.e., by running a web server that can be accessed externally in the remote workspace). Providing the same reproducible environment also helps opensource contributors to reproduce bugs and provide feedback most efficiently. They can use the same best practices as upstream maintainers.",[12,4909,4910,4911,4916,4917,4921],{},"Developers and DevOps engineers will be using the Web IDE in workspaces. Later, being able to ",[28,4912,4915],{"href":4913,"rel":4914},"https://gitlab.com/groups/gitlab-org/-/epics/10478",[],"connect their desktop client to workspaces",", they can take advantage of even more efficiency with the ",[28,4918,4920],{"href":4919},"/gitlab-duo-agent-platform/","most comprehensive AI-powered DevSecOps platform",": Code suggestions and more AI-powered workflows are just one fingertip away.",[12,4923,4924,4925,4930,4931,4936],{},"What will your teams build with remote development workspaces? Please share your experiences in the ",[28,4926,4929],{"href":4927,"rel":4928},"https://gitlab.com/gitlab-org/gitlab/-/issues/410031",[],"feedback issue",", blog about your setup, and join our ",[28,4932,4935],{"href":4933,"rel":4934},"https://forum.gitlab.com/",[],"community forum"," for more discussions.",[12,4938,4939,4940,4945,4946],{},"Cover image by ",[28,4941,4944],{"href":4942,"rel":4943},"https://unsplash.com/@nickkarvounis",[],"Nick Karvounis"," on ",[28,4947,4950],{"href":4948,"rel":4949},"https://unsplash.com/photos/SmIM3m8f3Pw",[],"Unsplash",[4952,4953,4954],"style",{},"html pre.shiki code .sD7c4, html code.shiki .sD7c4{--shiki-default:#D73A49}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 .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 .s7eDp, html code.shiki .s7eDp{--shiki-default:#6F42C1}html pre.shiki code .sYu0t, html code.shiki .sYu0t{--shiki-default:#005CC5}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .shJU0, html code.shiki .shJU0{--shiki-default:#22863A}html pre.shiki code .s9hmT, html code.shiki .s9hmT{--shiki-default:#B31D28}",{"title":309,"searchDepth":334,"depth":334,"links":4956},[4957,4958,4962,4963,4970,4973,4977,4982],{"id":163,"depth":334,"text":31},{"id":233,"depth":334,"text":37,"children":4959},[4960,4961],{"id":261,"depth":347,"text":45},{"id":379,"depth":347,"text":51},{"id":754,"depth":334,"text":755},{"id":907,"depth":334,"text":63,"children":4964},[4965,4966,4967,4968,4969],{"id":1127,"depth":347,"text":71},{"id":1607,"depth":347,"text":77},{"id":1854,"depth":347,"text":83},{"id":2192,"depth":347,"text":89},{"id":2354,"depth":347,"text":95},{"id":2374,"depth":334,"text":101,"children":4971},[4972],{"id":2739,"depth":347,"text":107},{"id":3178,"depth":334,"text":113,"children":4974},[4975,4976],{"id":3370,"depth":347,"text":121},{"id":3975,"depth":347,"text":127},{"id":4224,"depth":334,"text":133,"children":4978},[4979,4980,4981],{"id":4278,"depth":347,"text":141},{"id":4299,"depth":347,"text":147},{"id":4887,"depth":347,"text":153},{"id":4904,"depth":334,"text":159},"engineering","2023-07-13","Learn how to set up the requirements, manage Kubernetes clusters in different clouds, create the first workspaces and custom images, and get tips and troubleshooting.","md",null,false,"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749659883/Blog/Hero%20Images/post-cover-image.jpg",{},"/en-us/blog/set-up-infrastructure-for-cloud-development-environments",{"title":4993,"description":4985,"ogTitle":4993,"ogDescription":4985,"noIndex":4988,"ogImage":4989,"ogUrl":4994,"ogSiteName":4995,"ogType":4996,"canonicalUrls":4994},"Cloud infrastructure for on-demand development in GitLab","https://about.gitlab.com/blog/set-up-infrastructure-for-cloud-development-environments","https://about.gitlab.com","article","set-up-infrastructure-for-cloud-development-environments","en-us/blog/set-up-infrastructure-for-cloud-development-environments",[5000,5001,5002,5003,5004],"DevSecOps platform","careers","DevOps","workflow","cloud native","BlogPost","pPKrxgkLKhNTGxTFZkAZtEQgYBCjieK9dX0TPjFgtsM",{"logo":5008,"freeTrial":5013,"sales":5018,"login":5023,"items":5028,"search":5350,"minimal":5381,"duo":5400,"switchNav":5409,"pricingDeployment":5420},{"config":5009},{"href":5010,"dataGaName":5011,"dataGaLocation":5012},"/","gitlab logo","header",{"text":5014,"config":5015},"Get free trial",{"href":5016,"dataGaName":5017,"dataGaLocation":5012},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com&glm_content=default-saas-trial/","free trial",{"text":5019,"config":5020},"Request a demo",{"href":5021,"dataGaName":5022,"dataGaLocation":5012},"/sales/?contact-topic=request-demo","sales",{"text":5024,"config":5025},"Sign in",{"href":5026,"dataGaName":5027,"dataGaLocation":5012},"https://gitlab.com/users/sign_in/","sign in",[5029,5057,5156,5161,5272,5328],{"text":5030,"config":5031,"menu":5033},"Platform",{"dataNavLevelOne":5032},"platform",{"type":5034,"columns":5035},"cards",[5036,5042,5049],{"title":5030,"description":5037,"link":5038},"The intelligent orchestration platform for DevSecOps",{"text":5039,"config":5040},"Explore our Platform",{"href":5041,"dataGaName":5032,"dataGaLocation":5012},"/platform/",{"title":5043,"description":5044,"link":5045},"GitLab Duo Agent Platform","Agentic AI for the entire software lifecycle",{"text":5046,"config":5047},"Meet GitLab Duo",{"href":4919,"dataGaName":5048,"dataGaLocation":5012},"gitlab duo agent platform",{"title":5050,"description":5051,"link":5052},"Why GitLab","See the top reasons enterprises choose GitLab",{"text":5053,"config":5054},"Learn more",{"href":5055,"dataGaName":5056,"dataGaLocation":5012},"/why-gitlab/","why gitlab",{"text":5058,"left":485,"config":5059,"menu":5061},"Product",{"dataNavLevelOne":5060},"solutions",{"type":5062,"link":5063,"columns":5067,"feature":5135},"lists",{"text":5064,"config":5065},"View all Solutions",{"href":5066,"dataGaName":5060,"dataGaLocation":5012},"/solutions/",[5068,5091,5114],{"title":5069,"description":5070,"link":5071,"items":5076},"Automation","CI/CD and automation to accelerate deployment",{"config":5072},{"icon":5073,"href":5074,"dataGaName":5075,"dataGaLocation":5012},"AutomatedCodeAlt","/solutions/delivery-automation/","automated software delivery",[5077,5080,5083,5087],{"text":5078,"config":5079},"CI/CD",{"href":3985,"dataGaLocation":5012,"dataGaName":5078},{"text":5043,"config":5081},{"href":4919,"dataGaLocation":5012,"dataGaName":5082},"gitlab duo agent platform - product menu",{"text":5084,"config":5085},"Source Code Management",{"href":5086,"dataGaLocation":5012,"dataGaName":5084},"/solutions/source-code-management/",{"text":5088,"config":5089},"Automated Software Delivery",{"href":5074,"dataGaLocation":5012,"dataGaName":5090},"Automated software delivery",{"title":5092,"description":5093,"link":5094,"items":5099},"Security","Deliver code faster without compromising security",{"config":5095},{"href":5096,"dataGaName":5097,"dataGaLocation":5012,"icon":5098},"/solutions/application-security-testing/","security and compliance","ShieldCheckLight",[5100,5104,5109],{"text":5101,"config":5102},"Application Security Testing",{"href":5096,"dataGaName":5103,"dataGaLocation":5012},"Application security testing",{"text":5105,"config":5106},"Software Supply Chain Security",{"href":5107,"dataGaLocation":5012,"dataGaName":5108},"/solutions/supply-chain/","Software supply chain security",{"text":5110,"config":5111},"Software Compliance",{"href":5112,"dataGaName":5113,"dataGaLocation":5012},"/solutions/software-compliance/","software compliance",{"title":5115,"link":5116,"items":5121},"Measurement",{"config":5117},{"icon":5118,"href":5119,"dataGaName":5120,"dataGaLocation":5012},"DigitalTransformation","/solutions/visibility-measurement/","visibility and measurement",[5122,5126,5130],{"text":5123,"config":5124},"Visibility & Measurement",{"href":5119,"dataGaLocation":5012,"dataGaName":5125},"Visibility and Measurement",{"text":5127,"config":5128},"Value Stream Management",{"href":5129,"dataGaLocation":5012,"dataGaName":5127},"/solutions/value-stream-management/",{"text":5131,"config":5132},"Analytics & Insights",{"href":5133,"dataGaLocation":5012,"dataGaName":5134},"/solutions/analytics-and-insights/","Analytics and insights",{"title":5136,"type":5062,"items":5137},"GitLab for",[5138,5144,5150],{"text":5139,"config":5140},"Enterprise",{"icon":5141,"href":5142,"dataGaLocation":5012,"dataGaName":5143},"Building","/enterprise/","enterprise",{"text":5145,"config":5146},"Small Business",{"icon":5147,"href":5148,"dataGaLocation":5012,"dataGaName":5149},"Work","/small-business/","small business",{"text":5151,"config":5152},"Public Sector",{"icon":5153,"href":5154,"dataGaLocation":5012,"dataGaName":5155},"Organization","/solutions/public-sector/","public sector",{"text":5157,"config":5158},"Pricing",{"href":5159,"dataGaName":5160,"dataGaLocation":5012,"dataNavLevelOne":5160},"/pricing/","pricing",{"text":5162,"config":5163,"menu":5165},"Resources",{"dataNavLevelOne":5164},"resources",{"type":5062,"link":5166,"columns":5170,"feature":5261},{"text":5167,"config":5168},"View all resources",{"href":5169,"dataGaName":5164,"dataGaLocation":5012},"/resources/",[5171,5204,5231],{"title":5172,"items":5173},"Getting started",[5174,5179,5184,5189,5194,5199],{"text":5175,"config":5176},"Install",{"href":5177,"dataGaName":5178,"dataGaLocation":5012},"/install/","install",{"text":5180,"config":5181},"Quick start guides",{"href":5182,"dataGaName":5183,"dataGaLocation":5012},"/get-started/","quick setup checklists",{"text":5185,"config":5186},"Learn",{"href":5187,"dataGaLocation":5012,"dataGaName":5188},"https://university.gitlab.com/","learn",{"text":5190,"config":5191},"Product documentation",{"href":5192,"dataGaName":5193,"dataGaLocation":5012},"https://docs.gitlab.com/","product documentation",{"text":5195,"config":5196},"Best practice videos",{"href":5197,"dataGaName":5198,"dataGaLocation":5012},"/getting-started-videos/","best practice videos",{"text":5200,"config":5201},"Integrations",{"href":5202,"dataGaName":5203,"dataGaLocation":5012},"/integrations/","integrations",{"title":5205,"items":5206},"Discover",[5207,5212,5217,5222,5226],{"text":5208,"config":5209},"Customer success stories",{"href":5210,"dataGaName":5211,"dataGaLocation":5012},"/customers/","customer success stories",{"text":5213,"config":5214},"Blog",{"href":5215,"dataGaName":5216,"dataGaLocation":5012},"/blog/","blog",{"text":5218,"config":5219},"Demo Hub",{"href":5220,"dataGaName":5221,"dataGaLocation":5012},"/demo-hub/","demo hub",{"text":5223,"config":5224},"The Source",{"href":5225,"dataGaName":5216,"dataGaLocation":5012},"/the-source/",{"text":5227,"config":5228},"Remote",{"href":5229,"dataGaName":5230,"dataGaLocation":5012},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"title":5232,"items":5233},"Connect",[5234,5239,5242,5247,5251,5256],{"text":5235,"config":5236},"GitLab Services",{"href":5237,"dataGaName":5238,"dataGaLocation":5012},"/services/","services",{"text":153,"config":5240},{"href":5241,"dataGaName":4887,"dataGaLocation":5012},"https://contributors.gitlab.com",{"text":5243,"config":5244},"Community",{"href":5245,"dataGaName":5246,"dataGaLocation":5012},"/community/","community",{"text":5248,"config":5249},"Forum",{"href":4933,"dataGaName":5250,"dataGaLocation":5012},"forum",{"text":5252,"config":5253},"Events",{"href":5254,"dataGaName":5255,"dataGaLocation":5012},"/events/","events",{"text":5257,"config":5258},"Partners",{"href":5259,"dataGaName":5260,"dataGaLocation":5012},"/partners/","partners",{"config":5262,"title":5265,"text":5266,"link":5267},{"background":5263,"textColor":5264},"url('https://res.cloudinary.com/about-gitlab-com/image/upload/v1777322348/qpq8yrgn8knii57omj0c.png')","#000","What’s new in GitLab","Stay updated with our latest features and improvements.",{"text":5268,"config":5269},"Read the latest",{"href":5270,"dataGaName":5271,"dataGaLocation":5012},"/whats-new/","whats new",{"text":5273,"config":5274,"menu":5276},"Company",{"dataNavLevelOne":5275},"company",{"type":5062,"columns":5277},[5278],{"items":5279},[5280,5285,5291,5293,5298,5303,5308,5313,5318,5323],{"text":5281,"config":5282},"About",{"href":5283,"dataGaName":5284,"dataGaLocation":5012},"/company/","about",{"text":5286,"config":5287,"footerGa":5290},"Jobs",{"href":5288,"dataGaName":5289,"dataGaLocation":5012},"/jobs/","jobs",{"dataGaName":5289},{"text":5252,"config":5292},{"href":5254,"dataGaName":5255,"dataGaLocation":5012},{"text":5294,"config":5295},"Leadership",{"href":5296,"dataGaName":5297,"dataGaLocation":5012},"/company/team/e-group/","leadership",{"text":5299,"config":5300},"Handbook",{"href":5301,"dataGaName":5302,"dataGaLocation":5012},"https://handbook.gitlab.com/","handbook",{"text":5304,"config":5305},"Investor relations",{"href":5306,"dataGaName":5307,"dataGaLocation":5012},"https://ir.gitlab.com/overview/default.aspx","investor relations",{"text":5309,"config":5310},"Trust Center",{"href":5311,"dataGaName":5312,"dataGaLocation":5012},"/security/","trust center",{"text":5314,"config":5315},"AI Transparency Center",{"href":5316,"dataGaName":5317,"dataGaLocation":5012},"/ai-transparency-center/","ai transparency center",{"text":5319,"config":5320},"Newsletter",{"href":5321,"dataGaName":5322,"dataGaLocation":5012},"/company/contact/#contact-forms","newsletter",{"text":5324,"config":5325},"Press",{"href":5326,"dataGaName":5327,"dataGaLocation":5012},"/press/","press",{"text":5329,"config":5330,"menu":5331},"Contact us",{"dataNavLevelOne":5275},{"type":5062,"columns":5332},[5333],{"items":5334},[5335,5340,5345],{"text":5336,"config":5337},"Talk to sales",{"href":5338,"dataGaName":5339,"dataGaLocation":5012},"/sales/","talk to sales",{"text":5341,"config":5342},"Support portal",{"href":5343,"dataGaName":5344,"dataGaLocation":5012},"https://support.gitlab.com/hc/en-us","support portal",{"text":5346,"config":5347},"Customer portal",{"href":5348,"dataGaName":5349,"dataGaLocation":5012},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":5351,"login":5352,"suggestions":5359},"Close",{"text":5353,"link":5354},"To search repositories and projects, login to",{"text":5355,"config":5356},"gitlab.com",{"href":5026,"dataGaName":5357,"dataGaLocation":5358},"search login","search",{"text":5360,"default":5361},"Suggestions",[5362,5364,5368,5370,5374,5378],{"text":5043,"config":5363},{"href":4919,"dataGaName":5043,"dataGaLocation":5358},{"text":5365,"config":5366},"Code Suggestions (AI)",{"href":5367,"dataGaName":5365,"dataGaLocation":5358},"/solutions/code-suggestions/",{"text":5078,"config":5369},{"href":3985,"dataGaName":5078,"dataGaLocation":5358},{"text":5371,"config":5372},"GitLab on AWS",{"href":5373,"dataGaName":5371,"dataGaLocation":5358},"/partners/technology-partners/aws/",{"text":5375,"config":5376},"GitLab on Google Cloud",{"href":5377,"dataGaName":5375,"dataGaLocation":5358},"/partners/technology-partners/google-cloud-platform/",{"text":5379,"config":5380},"Why GitLab?",{"href":5055,"dataGaName":5379,"dataGaLocation":5358},{"freeTrial":5382,"mobileIcon":5387,"desktopIcon":5392,"secondaryButton":5395},{"text":5383,"config":5384},"Start free trial",{"href":5385,"dataGaName":5017,"dataGaLocation":5386},"https://gitlab.com/-/trials/new/","nav",{"altText":5388,"config":5389},"Gitlab Icon",{"src":5390,"dataGaName":5391,"dataGaLocation":5386},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203874/jypbw1jx72aexsoohd7x.svg","gitlab icon",{"altText":5388,"config":5393},{"src":5394,"dataGaName":5391,"dataGaLocation":5386},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203875/gs4c8p8opsgvflgkswz9.svg",{"text":5396,"config":5397},"Get Started",{"href":5398,"dataGaName":5399,"dataGaLocation":5386},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/get-started/","get started",{"freeTrial":5401,"mobileIcon":5405,"desktopIcon":5407},{"text":5402,"config":5403},"Learn more about GitLab Duo",{"href":4919,"dataGaName":5404,"dataGaLocation":5386},"gitlab duo",{"altText":5388,"config":5406},{"src":5390,"dataGaName":5391,"dataGaLocation":5386},{"altText":5388,"config":5408},{"src":5394,"dataGaName":5391,"dataGaLocation":5386},{"button":5410,"mobileIcon":5415,"desktopIcon":5417},{"text":5411,"config":5412},"/switch",{"href":5413,"dataGaName":5414,"dataGaLocation":5386},"#contact","switch",{"altText":5388,"config":5416},{"src":5390,"dataGaName":5391,"dataGaLocation":5386},{"altText":5388,"config":5418},{"src":5419,"dataGaName":5391,"dataGaLocation":5386},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1773335277/ohhpiuoxoldryzrnhfrh.png",{"freeTrial":5421,"mobileIcon":5426,"desktopIcon":5428},{"text":5422,"config":5423},"Back to pricing",{"href":5159,"dataGaName":5424,"dataGaLocation":5386,"icon":5425},"back to pricing","GoBack",{"altText":5388,"config":5427},{"src":5390,"dataGaName":5391,"dataGaLocation":5386},{"altText":5388,"config":5429},{"src":5394,"dataGaName":5391,"dataGaLocation":5386},{"title":5431,"titleMobile":5432,"button":5433,"config":5438},"Duo Agent Platform delivers 400% ROI, per new Forrester Consulting study.","400% ROI: Forrester TEI for GitLab Duo",{"text":5053,"config":5434},{"href":5435,"dataGaName":5436,"dataGaLocation":5437},"https://about.gitlab.com/blog/gitlab-duo-agent-platform-delivers-400-percent-roi/","forrester-tei-dap-banner","global-banner",{"layout":5439,"disabled":4988},"release",{"data":5441},{"text":5442,"source":5443,"edit":5449,"contribute":5454,"config":5459,"items":5464,"minimal":5671},"Git is a trademark of Software Freedom Conservancy and our use of 'GitLab' is under license",{"text":5444,"config":5445},"View page source",{"href":5446,"dataGaName":5447,"dataGaLocation":5448},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":5450,"config":5451},"Edit this page",{"href":5452,"dataGaName":5453,"dataGaLocation":5448},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":5455,"config":5456},"Please contribute",{"href":5457,"dataGaName":5458,"dataGaLocation":5448},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":5460,"facebook":5461,"youtube":5462,"linkedin":5463},"https://twitter.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[5465,5512,5563,5607,5639],{"title":5157,"links":5466,"subMenu":5481},[5467,5471,5476],{"text":5468,"config":5469},"View plans",{"href":5159,"dataGaName":5470,"dataGaLocation":5448},"view plans",{"text":5472,"config":5473},"Why Premium?",{"href":5474,"dataGaName":5475,"dataGaLocation":5448},"/pricing/premium/","why premium",{"text":5477,"config":5478},"Why Ultimate?",{"href":5479,"dataGaName":5480,"dataGaLocation":5448},"/pricing/ultimate/","why ultimate",[5482],{"title":5483,"links":5484},"Contact Us",[5485,5488,5490,5492,5497,5502,5507],{"text":5486,"config":5487},"Contact sales",{"href":5338,"dataGaName":5022,"dataGaLocation":5448},{"text":5341,"config":5489},{"href":5343,"dataGaName":5344,"dataGaLocation":5448},{"text":5346,"config":5491},{"href":5348,"dataGaName":5349,"dataGaLocation":5448},{"text":5493,"config":5494},"Status",{"href":5495,"dataGaName":5496,"dataGaLocation":5448},"https://status.gitlab.com/","status",{"text":5498,"config":5499},"Terms of use",{"href":5500,"dataGaName":5501,"dataGaLocation":5448},"/terms/","terms of use",{"text":5503,"config":5504},"Privacy statement",{"href":5505,"dataGaName":5506,"dataGaLocation":5448},"/privacy/","privacy statement",{"text":5508,"config":5509},"Cookie preferences",{"dataGaName":5510,"dataGaLocation":5448,"id":5511,"isOneTrustButton":485},"cookie preferences","ot-sdk-btn",{"title":5058,"links":5513,"subMenu":5521},[5514,5517],{"text":5000,"config":5515},{"href":5041,"dataGaName":5516,"dataGaLocation":5448},"devsecops platform",{"text":5518,"config":5519},"AI-Assisted Development",{"href":4919,"dataGaName":5520,"dataGaLocation":5448},"ai-assisted development",[5522],{"title":5523,"links":5524},"Topics",[5525,5530,5535,5539,5544,5549,5553,5558],{"text":5526,"config":5527},"CICD",{"href":5528,"dataGaName":5529,"dataGaLocation":5448},"/topics/ci-cd/","cicd",{"text":5531,"config":5532},"GitOps",{"href":5533,"dataGaName":5534,"dataGaLocation":5448},"/topics/gitops/","gitops",{"text":5002,"config":5536},{"href":5537,"dataGaName":5538,"dataGaLocation":5448},"/topics/devops/","devops",{"text":5540,"config":5541},"Version Control",{"href":5542,"dataGaName":5543,"dataGaLocation":5448},"/topics/version-control/","version control",{"text":5545,"config":5546},"DevSecOps",{"href":5547,"dataGaName":5548,"dataGaLocation":5448},"/topics/devsecops/","devsecops",{"text":5550,"config":5551},"Cloud Native",{"href":5552,"dataGaName":5004,"dataGaLocation":5448},"/topics/cloud-native/",{"text":5554,"config":5555},"AI for Coding",{"href":5556,"dataGaName":5557,"dataGaLocation":5448},"/topics/devops/ai-for-coding/","ai for coding",{"text":5559,"config":5560},"Agentic AI",{"href":5561,"dataGaName":5562,"dataGaLocation":5448},"/topics/agentic-ai/","agentic ai",{"title":5564,"links":5565},"Solutions",[5566,5568,5570,5575,5579,5582,5586,5589,5591,5594,5597,5602],{"text":5101,"config":5567},{"href":5096,"dataGaName":5101,"dataGaLocation":5448},{"text":5090,"config":5569},{"href":5074,"dataGaName":5075,"dataGaLocation":5448},{"text":5571,"config":5572},"Agile development",{"href":5573,"dataGaName":5574,"dataGaLocation":5448},"/solutions/agile-delivery/","agile delivery",{"text":5576,"config":5577},"SCM",{"href":5086,"dataGaName":5578,"dataGaLocation":5448},"source code management",{"text":5526,"config":5580},{"href":3985,"dataGaName":5581,"dataGaLocation":5448},"continuous integration & delivery",{"text":5583,"config":5584},"Value stream management",{"href":5129,"dataGaName":5585,"dataGaLocation":5448},"value stream management",{"text":5531,"config":5587},{"href":5588,"dataGaName":5534,"dataGaLocation":5448},"/solutions/gitops/",{"text":5139,"config":5590},{"href":5142,"dataGaName":5143,"dataGaLocation":5448},{"text":5592,"config":5593},"Small business",{"href":5148,"dataGaName":5149,"dataGaLocation":5448},{"text":5595,"config":5596},"Public sector",{"href":5154,"dataGaName":5155,"dataGaLocation":5448},{"text":5598,"config":5599},"Education",{"href":5600,"dataGaName":5601,"dataGaLocation":5448},"/solutions/education/","education",{"text":5603,"config":5604},"Financial services",{"href":5605,"dataGaName":5606,"dataGaLocation":5448},"/solutions/finance/","financial services",{"title":5162,"links":5608},[5609,5611,5613,5615,5618,5620,5623,5625,5627,5629,5631,5633,5635,5637],{"text":5175,"config":5610},{"href":5177,"dataGaName":5178,"dataGaLocation":5448},{"text":5180,"config":5612},{"href":5182,"dataGaName":5183,"dataGaLocation":5448},{"text":5185,"config":5614},{"href":5187,"dataGaName":5188,"dataGaLocation":5448},{"text":5190,"config":5616},{"href":5192,"dataGaName":5617,"dataGaLocation":5448},"docs",{"text":5213,"config":5619},{"href":5215,"dataGaName":5216,"dataGaLocation":5448},{"text":5621,"config":5622},"What's new",{"href":5270,"dataGaName":5271,"dataGaLocation":5448},{"text":5208,"config":5624},{"href":5210,"dataGaName":5211,"dataGaLocation":5448},{"text":5227,"config":5626},{"href":5229,"dataGaName":5230,"dataGaLocation":5448},{"text":5235,"config":5628},{"href":5237,"dataGaName":5238,"dataGaLocation":5448},{"text":153,"config":5630},{"href":5241,"dataGaName":4887,"dataGaLocation":5448},{"text":5243,"config":5632},{"href":5245,"dataGaName":5246,"dataGaLocation":5448},{"text":5248,"config":5634},{"href":4933,"dataGaName":5250,"dataGaLocation":5448},{"text":5252,"config":5636},{"href":5254,"dataGaName":5255,"dataGaLocation":5448},{"text":5257,"config":5638},{"href":5259,"dataGaName":5260,"dataGaLocation":5448},{"title":5273,"links":5640},[5641,5643,5645,5647,5649,5651,5655,5660,5662,5664,5666],{"text":5281,"config":5642},{"href":5283,"dataGaName":5275,"dataGaLocation":5448},{"text":5286,"config":5644},{"href":5288,"dataGaName":5289,"dataGaLocation":5448},{"text":5294,"config":5646},{"href":5296,"dataGaName":5297,"dataGaLocation":5448},{"text":5299,"config":5648},{"href":5301,"dataGaName":5302,"dataGaLocation":5448},{"text":5304,"config":5650},{"href":5306,"dataGaName":5307,"dataGaLocation":5448},{"text":5652,"config":5653},"Sustainability",{"href":5654,"dataGaName":5652,"dataGaLocation":5448},"/sustainability/",{"text":5656,"config":5657},"Diversity, inclusion and belonging (DIB)",{"href":5658,"dataGaName":5659,"dataGaLocation":5448},"/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":5309,"config":5661},{"href":5311,"dataGaName":5312,"dataGaLocation":5448},{"text":5319,"config":5663},{"href":5321,"dataGaName":5322,"dataGaLocation":5448},{"text":5324,"config":5665},{"href":5326,"dataGaName":5327,"dataGaLocation":5448},{"text":5667,"config":5668},"Modern Slavery Transparency Statement",{"href":5669,"dataGaName":5670,"dataGaLocation":5448},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"items":5672},[5673,5676,5679],{"text":5674,"config":5675},"Terms",{"href":5500,"dataGaName":5501,"dataGaLocation":5448},{"text":5677,"config":5678},"Cookies",{"dataGaName":5510,"dataGaLocation":5448,"id":5511,"isOneTrustButton":485},{"text":5680,"config":5681},"Privacy",{"href":5505,"dataGaName":5506,"dataGaLocation":5448},[5683],{"id":5684,"title":7,"body":4987,"config":5685,"content":5687,"description":4987,"extension":5691,"meta":5692,"navigation":485,"path":5693,"seo":5694,"stem":5695,"__hash__":5696},"blogAuthors/en-us/blog/authors/michael-friedrich.yml",{"template":5686},"BlogAuthor",{"name":7,"config":5688},{"headshot":5689,"ctfId":5690},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749659879/Blog/Author%20Headshots/dnsmichi-headshot.jpg","dnsmichi","yml",{},"/en-us/blog/authors/michael-friedrich",{},"en-us/blog/authors/michael-friedrich","lJ-nfRIhdG49Arfrxdn1Vv4UppwD51BB13S3HwIswt4",[5698,5707,5715],{"title":5699,"description":5700,"heroImage":5701,"category":4983,"date":5702,"authors":5703,"slug":5706,"externalUrl":4987},"Confidential AI for GitLab Self-Hosted","Give developers AI coding agents in GitLab Duo without source code leaving a hardware-encrypted boundary — no GPUs needed.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1773866173/vte9qh8rriznvyclhkes.png","2026-08-06",[5704,5705],"Mathias Ewald","Martin Paloncy, Edgeless Systems","confidential-ai-for-gitlab-self-hosted",{"title":5708,"description":5709,"heroImage":5710,"category":4983,"date":5711,"authors":5712,"slug":5714,"externalUrl":4987},"Green DevOps: Why carbon measurement belongs in your CI/CD pipeline","CI/CD pipelines have a hidden carbon cost. Here's why measuring it matters, and how you can get started with Eco CI and Carmen in GitLab.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1765809212/noh0mdfn9o94ry9ykura.png","2026-07-09",[5713],"Lysanne Pinto","green-devops-carbon-measurement-cicd-pipeline",{"title":5716,"description":5717,"heroImage":5718,"category":4983,"date":5719,"authors":5720,"slug":5722,"externalUrl":4987},"How to build CI/CD observability at scale","This practical guide to GitLab pipeline analytics helps self-managed users gain operational insights using Prometheus and Grafana.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1774465167/n5hlvrsrheadeccyr1oz.png","2026-04-28",[5721],"Paul Meresanu","how-to-build-ci-cd-observability-at-scale",{"promotions":5724},[5725,5739,5751,5763],{"id":5726,"categories":5727,"header":5729,"text":5730,"button":5731,"image":5736},"ai-modernization",[5728],"ai","Is AI achieving its promise at scale?","Quiz will take 5 minutes or less",{"text":5732,"config":5733},"Get your AI maturity score",{"href":5734,"dataGaName":5735,"dataGaLocation":5216},"/assessments/ai-modernization-assessment/","modernization assessment",{"config":5737},{"src":5738},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/qix0m7kwnd8x2fh1zq49.png",{"id":5740,"categories":5741,"header":5743,"text":5730,"button":5744,"image":5748},"devops-modernization",[5742,5548],"product","Are you just managing tools or shipping innovation?",{"text":5745,"config":5746},"Get your DevOps maturity score",{"href":5747,"dataGaName":5735,"dataGaLocation":5216},"/assessments/devops-modernization-assessment/",{"config":5749},{"src":5750},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138785/eg818fmakweyuznttgid.png",{"id":5752,"categories":5753,"header":5755,"text":5730,"button":5756,"image":5760},"security-modernization",[5754],"security","Are you trading speed for security?",{"text":5757,"config":5758},"Get your security maturity score",{"href":5759,"dataGaName":5735,"dataGaLocation":5216},"/assessments/security-modernization-assessment/",{"config":5761},{"src":5762},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/p4pbqd9nnjejg5ds6mdk.png",{"id":5764,"paths":5765,"header":5768,"text":5769,"button":5770,"image":5775},"github-azure-migration",[5766,5767],"migration-from-azure-devops-to-gitlab","integrating-azure-devops-scm-and-gitlab","Is your team ready for GitHub's Azure move?","GitHub is already rebuilding around Azure. Find out what it means for you.",{"text":5771,"config":5772},"See how GitLab compares to GitHub",{"href":5773,"dataGaName":5774,"dataGaLocation":5216},"/compare/gitlab-vs-github/github-azure-migration/","github azure migration",{"config":5776},{"src":5750},{"header":5778,"blurb":5779,"button":5780,"secondaryButton":5785},"Start building faster today","See what your team can do with the intelligent orchestration platform for DevSecOps.\n",{"text":5781,"config":5782},"Get your free trial",{"href":5783,"dataGaName":5017,"dataGaLocation":5784},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/","feature",{"text":5486,"config":5786},{"href":5338,"dataGaName":5022,"dataGaLocation":5784},1786803737501]