[{"data":1,"prerenderedAt":1820},["ShallowReactive",2],{"blog-en-docker-compose-to-kubernetes-migration":3,"blog-related-en-docker-compose-to-kubernetes-migration":1761,"blog-en-docker-compose-to-kubernetes-migration-alt":439},{"id":4,"title":5,"author":6,"body":7,"date":1746,"description":1747,"extension":1748,"image":259,"locale":1749,"meta":1750,"navigation":439,"path":1751,"seo":1752,"stem":1753,"tags":1754,"__hash__":1760},"blog\u002Fblog\u002Fen\u002Fdocker-compose-to-kubernetes-migration.md","Migration Guide: Docker Compose to Kubernetes","Kubo Team",{"type":8,"value":9,"toc":1722},"minimark",[10,14,24,29,32,157,166,174,178,181,186,253,457,477,481,489,493,569,573,625,640,644,727,736,740,754,758,828,834,1022,1026,1029,1297,1304,1308,1312,1315,1407,1414,1418,1424,1467,1487,1490,1493,1508,1512,1515,1519,1556,1648,1661,1665,1668,1700,1710,1718],[11,12,13],"p",{},"Docker Compose excels for development and simple deployments, but production environments demand the scalability, high availability, and operational automation that Kubernetes provides. However, migrating from Docker Compose to Kubernetes is not just about rewriting YAML. Success requires understanding the fundamental architectural differences in networking, storage, and secret management.",[11,15,16,23],{},[17,18,22],"a",{"href":19,"rel":20},"https:\u002F\u002Fkubo.hexabase.io\u002F",[21],"nofollow","Kubo"," provides production-grade Kubernetes infrastructure and supports smooth migrations from Docker Compose. This article walks through every phase of the migration process.",[25,26,28],"h2",{"id":27},"fundamental-differences-between-docker-compose-and-kubernetes","Fundamental Differences Between Docker Compose and Kubernetes",[11,30,31],{},"Before starting a migration, understanding the architectural differences between the two systems is essential.",[33,34,35,51],"table",{},[36,37,38],"thead",{},[39,40,41,45,48],"tr",{},[42,43,44],"th",{},"Aspect",[42,46,47],{},"Docker Compose",[42,49,50],{},"Kubernetes",[52,53,54,66,82,96,107,118,135,146],"tbody",{},[39,55,56,60,63],{},[57,58,59],"td",{},"Scope",[57,61,62],{},"Single host",[57,64,65],{},"Multi-node cluster",[39,67,68,71,79],{},[57,69,70],{},"Scaling",[57,72,73,74,78],{},"Manual (",[75,76,77],"code",{},"scale"," option)",[57,80,81],{},"Automatic (HPA \u002F VPA)",[39,83,84,87,93],{},[57,85,86],{},"Health Checks",[57,88,89,92],{},[75,90,91],{},"healthcheck"," directive",[57,94,95],{},"liveness \u002F readiness \u002F startup Probes",[39,97,98,101,104],{},[57,99,100],{},"Networking",[57,102,103],{},"Bridge networks",[57,105,106],{},"Service \u002F Ingress \u002F NetworkPolicy",[39,108,109,112,115],{},[57,110,111],{},"Storage",[57,113,114],{},"Bind mounts \u002F volumes",[57,116,117],{},"PersistentVolume \u002F PersistentVolumeClaim",[39,119,120,123,132],{},[57,121,122],{},"Secrets",[57,124,125,128,129],{},[75,126,127],{},".env"," files \u002F ",[75,130,131],{},"secrets",[57,133,134],{},"Secret \u002F External Secrets Operator",[39,136,137,140,143],{},[57,138,139],{},"Service Discovery",[57,141,142],{},"Container\u002Fservice names",[57,144,145],{},"DNS-based Services",[39,147,148,151,154],{},[57,149,150],{},"Rolling Updates",[57,152,153],{},"None",[57,155,156],{},"Deployment strategy",[11,158,159,160,165],{},"The ",[17,161,164],{"href":162,"rel":163},"https:\u002F\u002Fkubernetes.io\u002Fdocs\u002Ftasks\u002Fconfigure-pod-container\u002Ftranslate-compose-kubernetes\u002F",[21],"Kubernetes official documentation"," introduces the conversion process using Kompose, but this is only a starting point.",[11,167,168,173],{},[17,169,172],{"href":170,"rel":171},"https:\u002F\u002Fwww.hexabase.com\u002Fproduct\u002Fcaptain-ai\u002F",[21],"Captain.AI"," can assist your migration process with AI — from analyzing Docker Compose files to optimizing Kubernetes manifests.",[25,175,177],{"id":176},"phase-1-assessment-and-architecture-audit","Phase 1: Assessment and Architecture Audit",[11,179,180],{},"The first step is thoroughly analyzing your current Docker Compose configuration.",[182,183,185],"h3",{"id":184},"checklist","Checklist",[187,188,189,200,210,216,229,239],"ul",{},[190,191,192,196,197],"li",{},[193,194,195],"strong",{},"Service dependencies",": Map startup order defined by ",[75,198,199],{},"depends_on",[190,201,202,205,206,209],{},[193,203,204],{},"Volume mappings",": Distinguish development bind mounts (",[75,207,208],{},".\u002Fsrc:\u002Fapp",") from persistent data",[190,211,212,215],{},[193,213,214],{},"Network configuration",": Identify custom networks and inter-service communication",[190,217,218,221,222,224,225,228],{},[193,219,220],{},"Environment variables",": Catalog ",[75,223,127],{}," files, ",[75,226,227],{},"environment"," directives, and secrets",[190,230,231,234,235,238],{},[193,232,233],{},"Build contexts",": Identify ",[75,236,237],{},"build"," directives (K8S requires pre-built images)",[190,240,241,244,245,248,249,252],{},[193,242,243],{},"Resource constraints",": Note ",[75,246,247],{},"mem_limit",", ",[75,250,251],{},"cpus",", and similar settings",[254,255,260],"pre",{"className":256,"code":257,"language":258,"meta":259,"style":259},"language-yaml shiki shiki-themes tokyo-night","# Typical Docker Compose configuration\nservices:\n  web:\n    build: .\u002Fweb\n    ports:\n      - \"8080:8080\"\n    environment:\n      - database_url=postgres:--db:5432-myapp\n    depends_on:\n      - db\n    volumes:\n      - .-web:-app  # development bind mount\n  \n  db:\n    image: postgres:16\n    volumes:\n      - db-data:-var-lib-postgresql-data\n    environment:\n      - POSTGRES_PASSWORD=secret\n\nvolumes:\n  db-data:\n","yaml","",[75,261,262,271,282,290,303,311,327,335,343,351,359,367,378,385,393,404,411,419,426,434,441,449],{"__ignoreMap":259},[263,264,267],"span",{"class":265,"line":266},"line",1,[263,268,270],{"class":269},"sbD-w","# Typical Docker Compose configuration\n",[263,272,274,278],{"class":265,"line":273},2,[263,275,277],{"class":276},"s0U2E","services",[263,279,281],{"class":280},"sAklC",":\n",[263,283,285,288],{"class":265,"line":284},3,[263,286,287],{"class":276},"  web",[263,289,281],{"class":280},[263,291,293,296,299],{"class":265,"line":292},4,[263,294,295],{"class":276},"    build",[263,297,298],{"class":280},":",[263,300,302],{"class":301},"sPY7s"," .\u002Fweb\n",[263,304,306,309],{"class":265,"line":305},5,[263,307,308],{"class":276},"    ports",[263,310,281],{"class":280},[263,312,314,318,321,324],{"class":265,"line":313},6,[263,315,317],{"class":316},"sgJMe","      -",[263,319,320],{"class":280}," \"",[263,322,323],{"class":301},"8080:8080",[263,325,326],{"class":280},"\"\n",[263,328,330,333],{"class":265,"line":329},7,[263,331,332],{"class":276},"    environment",[263,334,281],{"class":280},[263,336,338,340],{"class":265,"line":337},8,[263,339,317],{"class":316},[263,341,342],{"class":301}," database_url=postgres:--db:5432-myapp\n",[263,344,346,349],{"class":265,"line":345},9,[263,347,348],{"class":276},"    depends_on",[263,350,281],{"class":280},[263,352,354,356],{"class":265,"line":353},10,[263,355,317],{"class":316},[263,357,358],{"class":301}," db\n",[263,360,362,365],{"class":265,"line":361},11,[263,363,364],{"class":276},"    volumes",[263,366,281],{"class":280},[263,368,370,372,375],{"class":265,"line":369},12,[263,371,317],{"class":316},[263,373,374],{"class":301}," .-web:-app",[263,376,377],{"class":269},"  # development bind mount\n",[263,379,381],{"class":265,"line":380},13,[263,382,384],{"class":383},"sGX4V","  \n",[263,386,388,391],{"class":265,"line":387},14,[263,389,390],{"class":276},"  db",[263,392,281],{"class":280},[263,394,396,399,401],{"class":265,"line":395},15,[263,397,398],{"class":276},"    image",[263,400,298],{"class":280},[263,402,403],{"class":301}," postgres:16\n",[263,405,407,409],{"class":265,"line":406},16,[263,408,364],{"class":276},[263,410,281],{"class":280},[263,412,414,416],{"class":265,"line":413},17,[263,415,317],{"class":316},[263,417,418],{"class":301}," db-data:-var-lib-postgresql-data\n",[263,420,422,424],{"class":265,"line":421},18,[263,423,332],{"class":276},[263,425,281],{"class":280},[263,427,429,431],{"class":265,"line":428},19,[263,430,317],{"class":316},[263,432,433],{"class":301}," POSTGRES_PASSWORD=secret\n",[263,435,437],{"class":265,"line":436},20,[263,438,440],{"emptyLinePlaceholder":439},true,"\n",[263,442,444,447],{"class":265,"line":443},21,[263,445,446],{"class":276},"volumes",[263,448,281],{"class":280},[263,450,452,455],{"class":265,"line":451},22,[263,453,454],{"class":276},"  db-data",[263,456,281],{"class":280},[11,458,459,462,463,466,467,470,471,476],{},[193,460,461],{},"Critical",": Development bind mounts (",[75,464,465],{},".\u002Fweb:\u002Fapp",") ",[193,468,469],{},"cannot be migrated"," to Kubernetes. Application code must be included in the image. The ",[17,472,475],{"href":473,"rel":474},"https:\u002F\u002Finstitute.sfeir.com\u002Fen\u002Fkubernetes-training\u002Fmigrate-docker-compose-kubernetes-guide-transition\u002F",[21],"SFEIR migration guide"," emphasizes this point.",[25,478,480],{"id":479},"phase-2-automated-conversion-with-kompose","Phase 2: Automated Conversion with Kompose",[11,482,483,488],{},[17,484,487],{"href":485,"rel":486},"https:\u002F\u002Fkompose.io\u002F",[21],"Kompose"," automatically converts Docker Compose files into Kubernetes manifests.",[182,490,492],{"id":491},"installation","Installation",[254,494,498],{"className":495,"code":496,"language":497,"meta":259,"style":259},"language-bash shiki shiki-themes tokyo-night","# macOS\nbrew install kompose\n\n# Linux\ncurl -L https:\u002F\u002Fgithub.com\u002Fkubernetes\u002Fkompose\u002Freleases\u002Fdownload\u002Fv1.34.0\u002Fkompose-linux-amd64 -o kompose\nchmod +x kompose && sudo mv .\u002Fkompose \u002Fusr\u002Flocal\u002Fbin\u002Fkompose\n","bash",[75,499,500,505,517,521,526,543],{"__ignoreMap":259},[263,501,502],{"class":265,"line":266},[263,503,504],{"class":269},"# macOS\n",[263,506,507,511,514],{"class":265,"line":273},[263,508,510],{"class":509},"sE3pS","brew",[263,512,513],{"class":301}," install",[263,515,516],{"class":301}," kompose\n",[263,518,519],{"class":265,"line":284},[263,520,440],{"emptyLinePlaceholder":439},[263,522,523],{"class":265,"line":292},[263,524,525],{"class":269},"# Linux\n",[263,527,528,531,535,538,541],{"class":265,"line":305},[263,529,530],{"class":509},"curl",[263,532,534],{"class":533},"sT800"," -L",[263,536,537],{"class":301}," https:\u002F\u002Fgithub.com\u002Fkubernetes\u002Fkompose\u002Freleases\u002Fdownload\u002Fv1.34.0\u002Fkompose-linux-amd64",[263,539,540],{"class":533}," -o",[263,542,516],{"class":301},[263,544,545,548,551,554,557,560,563,566],{"class":265,"line":313},[263,546,547],{"class":509},"chmod",[263,549,550],{"class":301}," +x",[263,552,553],{"class":301}," kompose",[263,555,556],{"class":280}," &&",[263,558,559],{"class":509}," sudo",[263,561,562],{"class":301}," mv",[263,564,565],{"class":301}," .\u002Fkompose",[263,567,568],{"class":301}," \u002Fusr\u002Flocal\u002Fbin\u002Fkompose\n",[182,570,572],{"id":571},"basic-conversion","Basic Conversion",[254,574,576],{"className":495,"code":575,"language":497,"meta":259,"style":259},"# Convert docker-compose.yml to Kubernetes manifests\nkompose convert\n\n# Example output:\n# INFO Kubernetes file \"web-service.yaml\" created\n# INFO Kubernetes file \"db-service.yaml\" created\n# INFO Kubernetes file \"web-deployment.yaml\" created\n# INFO Kubernetes file \"db-deployment.yaml\" created\n# INFO Kubernetes file \"db-data-persistentvolumeclaim.yaml\" created\n",[75,577,578,583,591,595,600,605,610,615,620],{"__ignoreMap":259},[263,579,580],{"class":265,"line":266},[263,581,582],{"class":269},"# Convert docker-compose.yml to Kubernetes manifests\n",[263,584,585,588],{"class":265,"line":273},[263,586,587],{"class":509},"kompose",[263,589,590],{"class":301}," convert\n",[263,592,593],{"class":265,"line":284},[263,594,440],{"emptyLinePlaceholder":439},[263,596,597],{"class":265,"line":292},[263,598,599],{"class":269},"# Example output:\n",[263,601,602],{"class":265,"line":305},[263,603,604],{"class":269},"# INFO Kubernetes file \"web-service.yaml\" created\n",[263,606,607],{"class":265,"line":313},[263,608,609],{"class":269},"# INFO Kubernetes file \"db-service.yaml\" created\n",[263,611,612],{"class":265,"line":329},[263,613,614],{"class":269},"# INFO Kubernetes file \"web-deployment.yaml\" created\n",[263,616,617],{"class":265,"line":337},[263,618,619],{"class":269},"# INFO Kubernetes file \"db-deployment.yaml\" created\n",[263,621,622],{"class":265,"line":345},[263,623,624],{"class":269},"# INFO Kubernetes file \"db-data-persistentvolumeclaim.yaml\" created\n",[11,626,627,628,631,632,635,636,639],{},"Kompose generates two Kubernetes resources from each Docker Compose service: a ",[193,629,630],{},"Deployment"," and a ",[193,633,634],{},"Service",". Volumes are converted to ",[193,637,638],{},"PersistentVolumeClaims",".",[182,641,643],{"id":642},"customization-with-kompose-labels","Customization with Kompose Labels",[254,645,647],{"className":256,"code":646,"language":258,"meta":259,"style":259},"services:\n  web:\n    image: myapp:latest\n    ports:\n      - \"8080:8080\"\n    labels:\n      kompose.service.type: LoadBalancer\n      kompose.service.expose: \"myapp.example.com\"\n      kompose.image-pull-policy: Always\n",[75,648,649,655,661,670,676,686,693,703,717],{"__ignoreMap":259},[263,650,651,653],{"class":265,"line":266},[263,652,277],{"class":276},[263,654,281],{"class":280},[263,656,657,659],{"class":265,"line":273},[263,658,287],{"class":276},[263,660,281],{"class":280},[263,662,663,665,667],{"class":265,"line":284},[263,664,398],{"class":276},[263,666,298],{"class":280},[263,668,669],{"class":301}," myapp:latest\n",[263,671,672,674],{"class":265,"line":292},[263,673,308],{"class":276},[263,675,281],{"class":280},[263,677,678,680,682,684],{"class":265,"line":305},[263,679,317],{"class":316},[263,681,320],{"class":280},[263,683,323],{"class":301},[263,685,326],{"class":280},[263,687,688,691],{"class":265,"line":313},[263,689,690],{"class":276},"    labels",[263,692,281],{"class":280},[263,694,695,698,700],{"class":265,"line":329},[263,696,697],{"class":276},"      kompose.service.type",[263,699,298],{"class":280},[263,701,702],{"class":301}," LoadBalancer\n",[263,704,705,708,710,712,715],{"class":265,"line":337},[263,706,707],{"class":276},"      kompose.service.expose",[263,709,298],{"class":280},[263,711,320],{"class":280},[263,713,714],{"class":301},"myapp.example.com",[263,716,326],{"class":280},[263,718,719,722,724],{"class":265,"line":345},[263,720,721],{"class":276},"      kompose.image-pull-policy",[263,723,298],{"class":280},[263,725,726],{"class":301}," Always\n",[11,728,729,730,735],{},"However, as the ",[17,731,734],{"href":732,"rel":733},"https:\u002F\u002Fsupport.tools\u002Fdocker-compose-to-kubernetes-migration-guide\u002F",[21],"Support Tools migration guide"," points out, Kompose \"creates a functional base but is rarely optimal for production,\" necessitating refinement in the next phase.",[25,737,739],{"id":738},"phase-3-production-ready-with-helm-kustomize","Phase 3: Production-Ready with Helm \u002F Kustomize",[11,741,742,743,748,749,639],{},"Elevate Kompose-generated manifests to production quality using ",[17,744,747],{"href":745,"rel":746},"https:\u002F\u002Fhelm.sh\u002F",[21],"Helm"," or ",[17,750,753],{"href":751,"rel":752},"https:\u002F\u002Fkustomize.io\u002F",[21],"Kustomize",[182,755,757],{"id":756},"migrating-to-helm-charts","Migrating to Helm Charts",[254,759,761],{"className":495,"code":760,"language":497,"meta":259,"style":259},"# Scaffold a Helm chart\nhelm create myapp\n\n# Directory structure\nmyapp\u002F\n  Chart.yaml\n  values.yaml\n  templates\u002F\n    deployment.yaml\n    service.yaml\n    ingress.yaml\n    hpa.yaml\n",[75,762,763,768,779,783,788,793,798,803,808,813,818,823],{"__ignoreMap":259},[263,764,765],{"class":265,"line":266},[263,766,767],{"class":269},"# Scaffold a Helm chart\n",[263,769,770,773,776],{"class":265,"line":273},[263,771,772],{"class":509},"helm",[263,774,775],{"class":301}," create",[263,777,778],{"class":301}," myapp\n",[263,780,781],{"class":265,"line":284},[263,782,440],{"emptyLinePlaceholder":439},[263,784,785],{"class":265,"line":292},[263,786,787],{"class":269},"# Directory structure\n",[263,789,790],{"class":265,"line":305},[263,791,792],{"class":509},"myapp\u002F\n",[263,794,795],{"class":265,"line":313},[263,796,797],{"class":509},"  Chart.yaml\n",[263,799,800],{"class":265,"line":329},[263,801,802],{"class":509},"  values.yaml\n",[263,804,805],{"class":265,"line":337},[263,806,807],{"class":509},"  templates\u002F\n",[263,809,810],{"class":265,"line":345},[263,811,812],{"class":509},"    deployment.yaml\n",[263,814,815],{"class":265,"line":353},[263,816,817],{"class":509},"    service.yaml\n",[263,819,820],{"class":265,"line":361},[263,821,822],{"class":509},"    ingress.yaml\n",[263,824,825],{"class":265,"line":369},[263,826,827],{"class":509},"    hpa.yaml\n",[11,829,830,831,298],{},"Externalize environment-specific settings in ",[75,832,833],{},"values.yaml",[254,835,837],{"className":256,"code":836,"language":258,"meta":259,"style":259},"# values.yaml\nreplicaCount: 3\nimage:\n  repository: harbor.example.com\u002Fmyproject\u002Fweb\n  tag: \"1.0.0\"\n  pullPolicy: IfNotPresent\nresources:\n  limits:\n    cpu: 500m\n    memory: 512Mi\n  requests:\n    cpu: 100m\n    memory: 128Mi\ningress:\n  enabled: true\n  hosts:\n    - host: myapp.example.com\n      paths:\n        - path: \u002F\n          pathType: Prefix\n",[75,838,839,844,855,862,872,886,896,903,910,920,930,937,946,955,962,972,979,992,999,1012],{"__ignoreMap":259},[263,840,841],{"class":265,"line":266},[263,842,843],{"class":269},"# values.yaml\n",[263,845,846,849,851],{"class":265,"line":273},[263,847,848],{"class":276},"replicaCount",[263,850,298],{"class":280},[263,852,854],{"class":853},"sOJ5S"," 3\n",[263,856,857,860],{"class":265,"line":284},[263,858,859],{"class":276},"image",[263,861,281],{"class":280},[263,863,864,867,869],{"class":265,"line":292},[263,865,866],{"class":276},"  repository",[263,868,298],{"class":280},[263,870,871],{"class":301}," harbor.example.com\u002Fmyproject\u002Fweb\n",[263,873,874,877,879,881,884],{"class":265,"line":305},[263,875,876],{"class":276},"  tag",[263,878,298],{"class":280},[263,880,320],{"class":280},[263,882,883],{"class":301},"1.0.0",[263,885,326],{"class":280},[263,887,888,891,893],{"class":265,"line":313},[263,889,890],{"class":276},"  pullPolicy",[263,892,298],{"class":280},[263,894,895],{"class":301}," IfNotPresent\n",[263,897,898,901],{"class":265,"line":329},[263,899,900],{"class":276},"resources",[263,902,281],{"class":280},[263,904,905,908],{"class":265,"line":337},[263,906,907],{"class":276},"  limits",[263,909,281],{"class":280},[263,911,912,915,917],{"class":265,"line":345},[263,913,914],{"class":276},"    cpu",[263,916,298],{"class":280},[263,918,919],{"class":301}," 500m\n",[263,921,922,925,927],{"class":265,"line":353},[263,923,924],{"class":276},"    memory",[263,926,298],{"class":280},[263,928,929],{"class":301}," 512Mi\n",[263,931,932,935],{"class":265,"line":361},[263,933,934],{"class":276},"  requests",[263,936,281],{"class":280},[263,938,939,941,943],{"class":265,"line":369},[263,940,914],{"class":276},[263,942,298],{"class":280},[263,944,945],{"class":301}," 100m\n",[263,947,948,950,952],{"class":265,"line":380},[263,949,924],{"class":276},[263,951,298],{"class":280},[263,953,954],{"class":301}," 128Mi\n",[263,956,957,960],{"class":265,"line":387},[263,958,959],{"class":276},"ingress",[263,961,281],{"class":280},[263,963,964,967,969],{"class":265,"line":395},[263,965,966],{"class":276},"  enabled",[263,968,298],{"class":280},[263,970,971],{"class":853}," true\n",[263,973,974,977],{"class":265,"line":406},[263,975,976],{"class":276},"  hosts",[263,978,281],{"class":280},[263,980,981,984,987,989],{"class":265,"line":413},[263,982,983],{"class":316},"    -",[263,985,986],{"class":276}," host",[263,988,298],{"class":280},[263,990,991],{"class":301}," myapp.example.com\n",[263,993,994,997],{"class":265,"line":421},[263,995,996],{"class":276},"      paths",[263,998,281],{"class":280},[263,1000,1001,1004,1007,1009],{"class":265,"line":428},[263,1002,1003],{"class":316},"        -",[263,1005,1006],{"class":276}," path",[263,1008,298],{"class":280},[263,1010,1011],{"class":301}," \u002F\n",[263,1013,1014,1017,1019],{"class":265,"line":436},[263,1015,1016],{"class":276},"          pathType",[263,1018,298],{"class":280},[263,1020,1021],{"class":301}," Prefix\n",[182,1023,1025],{"id":1024},"essential-additions","Essential Additions",[11,1027,1028],{},"Add production-critical configurations that Kompose doesn't auto-generate:",[254,1030,1032],{"className":256,"code":1031,"language":258,"meta":259,"style":259},"# Health checks (Probes)\nlivenessProbe:\n  httpGet:\n    path: \u002Fhealthz\n    port: 8080\n  initialDelaySeconds: 15\n  periodSeconds: 10\nreadinessProbe:\n  httpGet:\n    path: \u002Fready\n    port: 8080\n  initialDelaySeconds: 5\n  periodSeconds: 5\n\n# Resource limits\nresources:\n  requests:\n    cpu: 100m\n    memory: 128Mi\n  limits:\n    cpu: 500m\n    memory: 512Mi\n\n# Pod Disruption Budget\napiVersion: policy\u002Fv1\nkind: PodDisruptionBudget\nmetadata:\n  name: web-pdb\nspec:\n  minAvailable: 1\n  selector:\n    matchLabels:\n      app: web\n",[75,1033,1034,1039,1046,1053,1063,1073,1083,1093,1100,1106,1115,1123,1132,1140,1144,1149,1155,1161,1169,1177,1183,1191,1199,1204,1210,1221,1232,1240,1251,1259,1270,1278,1286],{"__ignoreMap":259},[263,1035,1036],{"class":265,"line":266},[263,1037,1038],{"class":269},"# Health checks (Probes)\n",[263,1040,1041,1044],{"class":265,"line":273},[263,1042,1043],{"class":276},"livenessProbe",[263,1045,281],{"class":280},[263,1047,1048,1051],{"class":265,"line":284},[263,1049,1050],{"class":276},"  httpGet",[263,1052,281],{"class":280},[263,1054,1055,1058,1060],{"class":265,"line":292},[263,1056,1057],{"class":276},"    path",[263,1059,298],{"class":280},[263,1061,1062],{"class":301}," \u002Fhealthz\n",[263,1064,1065,1068,1070],{"class":265,"line":305},[263,1066,1067],{"class":276},"    port",[263,1069,298],{"class":280},[263,1071,1072],{"class":853}," 8080\n",[263,1074,1075,1078,1080],{"class":265,"line":313},[263,1076,1077],{"class":276},"  initialDelaySeconds",[263,1079,298],{"class":280},[263,1081,1082],{"class":853}," 15\n",[263,1084,1085,1088,1090],{"class":265,"line":329},[263,1086,1087],{"class":276},"  periodSeconds",[263,1089,298],{"class":280},[263,1091,1092],{"class":853}," 10\n",[263,1094,1095,1098],{"class":265,"line":337},[263,1096,1097],{"class":276},"readinessProbe",[263,1099,281],{"class":280},[263,1101,1102,1104],{"class":265,"line":345},[263,1103,1050],{"class":276},[263,1105,281],{"class":280},[263,1107,1108,1110,1112],{"class":265,"line":353},[263,1109,1057],{"class":276},[263,1111,298],{"class":280},[263,1113,1114],{"class":301}," \u002Fready\n",[263,1116,1117,1119,1121],{"class":265,"line":361},[263,1118,1067],{"class":276},[263,1120,298],{"class":280},[263,1122,1072],{"class":853},[263,1124,1125,1127,1129],{"class":265,"line":369},[263,1126,1077],{"class":276},[263,1128,298],{"class":280},[263,1130,1131],{"class":853}," 5\n",[263,1133,1134,1136,1138],{"class":265,"line":380},[263,1135,1087],{"class":276},[263,1137,298],{"class":280},[263,1139,1131],{"class":853},[263,1141,1142],{"class":265,"line":387},[263,1143,440],{"emptyLinePlaceholder":439},[263,1145,1146],{"class":265,"line":395},[263,1147,1148],{"class":269},"# Resource limits\n",[263,1150,1151,1153],{"class":265,"line":406},[263,1152,900],{"class":276},[263,1154,281],{"class":280},[263,1156,1157,1159],{"class":265,"line":413},[263,1158,934],{"class":276},[263,1160,281],{"class":280},[263,1162,1163,1165,1167],{"class":265,"line":421},[263,1164,914],{"class":276},[263,1166,298],{"class":280},[263,1168,945],{"class":301},[263,1170,1171,1173,1175],{"class":265,"line":428},[263,1172,924],{"class":276},[263,1174,298],{"class":280},[263,1176,954],{"class":301},[263,1178,1179,1181],{"class":265,"line":436},[263,1180,907],{"class":276},[263,1182,281],{"class":280},[263,1184,1185,1187,1189],{"class":265,"line":443},[263,1186,914],{"class":276},[263,1188,298],{"class":280},[263,1190,919],{"class":301},[263,1192,1193,1195,1197],{"class":265,"line":451},[263,1194,924],{"class":276},[263,1196,298],{"class":280},[263,1198,929],{"class":301},[263,1200,1202],{"class":265,"line":1201},23,[263,1203,440],{"emptyLinePlaceholder":439},[263,1205,1207],{"class":265,"line":1206},24,[263,1208,1209],{"class":269},"# Pod Disruption Budget\n",[263,1211,1213,1216,1218],{"class":265,"line":1212},25,[263,1214,1215],{"class":276},"apiVersion",[263,1217,298],{"class":280},[263,1219,1220],{"class":301}," policy\u002Fv1\n",[263,1222,1224,1227,1229],{"class":265,"line":1223},26,[263,1225,1226],{"class":276},"kind",[263,1228,298],{"class":280},[263,1230,1231],{"class":301}," PodDisruptionBudget\n",[263,1233,1235,1238],{"class":265,"line":1234},27,[263,1236,1237],{"class":276},"metadata",[263,1239,281],{"class":280},[263,1241,1243,1246,1248],{"class":265,"line":1242},28,[263,1244,1245],{"class":276},"  name",[263,1247,298],{"class":280},[263,1249,1250],{"class":301}," web-pdb\n",[263,1252,1254,1257],{"class":265,"line":1253},29,[263,1255,1256],{"class":276},"spec",[263,1258,281],{"class":280},[263,1260,1262,1265,1267],{"class":265,"line":1261},30,[263,1263,1264],{"class":276},"  minAvailable",[263,1266,298],{"class":280},[263,1268,1269],{"class":853}," 1\n",[263,1271,1273,1276],{"class":265,"line":1272},31,[263,1274,1275],{"class":276},"  selector",[263,1277,281],{"class":280},[263,1279,1281,1284],{"class":265,"line":1280},32,[263,1282,1283],{"class":276},"    matchLabels",[263,1285,281],{"class":280},[263,1287,1289,1292,1294],{"class":265,"line":1288},33,[263,1290,1291],{"class":276},"      app",[263,1293,298],{"class":280},[263,1295,1296],{"class":301}," web\n",[11,1298,1299,1300,1303],{},"On ",[17,1301,22],{"href":19,"rel":1302},[21],"'s Kubernetes environment, Helm chart deployments are the standard workflow.",[25,1305,1307],{"id":1306},"phase-4-storage-and-network-migration","Phase 4: Storage and Network Migration",[182,1309,1311],{"id":1310},"storage-migration","Storage Migration",[11,1313,1314],{},"Map Docker Compose volumes to Kubernetes PersistentVolumeClaims (PVCs):",[254,1316,1318],{"className":256,"code":1317,"language":258,"meta":259,"style":259},"apiVersion: v1\nkind: PersistentVolumeClaim\nmetadata:\n  name: db-data\nspec:\n  accessModes:\n    - ReadWriteOnce\n  storageClassName: standard\n  resources:\n    requests:\n      storage: 10Gi\n",[75,1319,1320,1329,1338,1344,1353,1359,1366,1373,1383,1390,1397],{"__ignoreMap":259},[263,1321,1322,1324,1326],{"class":265,"line":266},[263,1323,1215],{"class":276},[263,1325,298],{"class":280},[263,1327,1328],{"class":301}," v1\n",[263,1330,1331,1333,1335],{"class":265,"line":273},[263,1332,1226],{"class":276},[263,1334,298],{"class":280},[263,1336,1337],{"class":301}," PersistentVolumeClaim\n",[263,1339,1340,1342],{"class":265,"line":284},[263,1341,1237],{"class":276},[263,1343,281],{"class":280},[263,1345,1346,1348,1350],{"class":265,"line":292},[263,1347,1245],{"class":276},[263,1349,298],{"class":280},[263,1351,1352],{"class":301}," db-data\n",[263,1354,1355,1357],{"class":265,"line":305},[263,1356,1256],{"class":276},[263,1358,281],{"class":280},[263,1360,1361,1364],{"class":265,"line":313},[263,1362,1363],{"class":276},"  accessModes",[263,1365,281],{"class":280},[263,1367,1368,1370],{"class":265,"line":329},[263,1369,983],{"class":316},[263,1371,1372],{"class":301}," ReadWriteOnce\n",[263,1374,1375,1378,1380],{"class":265,"line":337},[263,1376,1377],{"class":276},"  storageClassName",[263,1379,298],{"class":280},[263,1381,1382],{"class":301}," standard\n",[263,1384,1385,1388],{"class":265,"line":345},[263,1386,1387],{"class":276},"  resources",[263,1389,281],{"class":280},[263,1391,1392,1395],{"class":265,"line":353},[263,1393,1394],{"class":276},"    requests",[263,1396,281],{"class":280},[263,1398,1399,1402,1404],{"class":265,"line":361},[263,1400,1401],{"class":276},"      storage",[263,1403,298],{"class":280},[263,1405,1406],{"class":301}," 10Gi\n",[11,1408,1409,1410,1413],{},"Consider using ",[193,1411,1412],{},"StatefulSets"," for database persistence. StatefulSets provide stable network identities and persistent storage, making them ideal for database workloads.",[182,1415,1417],{"id":1416},"secret-management","Secret Management",[11,1419,1420,1421,1423],{},"Migrate ",[75,1422,127],{}," file secrets to Kubernetes Secrets:",[254,1425,1427],{"className":495,"code":1426,"language":497,"meta":259,"style":259},"# Create a Secret\nkubectl create secret generic db-credentials \\\n  --from-literal=POSTGRES_PASSWORD=secure-password\n\n# Or integrate with external secret stores via External Secrets Operator\n",[75,1428,1429,1434,1453,1458,1462],{"__ignoreMap":259},[263,1430,1431],{"class":265,"line":266},[263,1432,1433],{"class":269},"# Create a Secret\n",[263,1435,1436,1439,1441,1444,1447,1450],{"class":265,"line":273},[263,1437,1438],{"class":509},"kubectl",[263,1440,775],{"class":301},[263,1442,1443],{"class":301}," secret",[263,1445,1446],{"class":301}," generic",[263,1448,1449],{"class":301}," db-credentials",[263,1451,1452],{"class":280}," \\\n",[263,1454,1455],{"class":265,"line":284},[263,1456,1457],{"class":533},"  --from-literal=POSTGRES_PASSWORD=secure-password\n",[263,1459,1460],{"class":265,"line":292},[263,1461,440],{"emptyLinePlaceholder":439},[263,1463,1464],{"class":265,"line":305},[263,1465,1466],{"class":269},"# Or integrate with external secret stores via External Secrets Operator\n",[11,1468,1469,1470,748,1475,1480,1481,1486],{},"For integration with ",[17,1471,1474],{"href":1472,"rel":1473},"https:\u002F\u002Fwww.vaultproject.io\u002F",[21],"HashiCorp Vault",[17,1476,1479],{"href":1477,"rel":1478},"https:\u002F\u002Faws.amazon.com\u002Fsecrets-manager\u002F",[21],"AWS Secrets Manager",", the ",[17,1482,1485],{"href":1483,"rel":1484},"https:\u002F\u002Fexternal-secrets.io\u002F",[21],"External Secrets Operator"," is recommended.",[182,1488,100],{"id":1489},"networking",[11,1491,1492],{},"Docker Compose's service-name-based communication migrates to Kubernetes DNS-based service discovery:",[254,1494,1496],{"className":256,"code":1495,"language":258,"meta":259,"style":259},"# Docker Compose: DATABASE_URL=postgres:\u002F\u002Fdb:5432\u002Fmyapp\n# Kubernetes:     DATABASE_URL=postgres:\u002F\u002Fdb-service.default.svc.cluster.local:5432\u002Fmyapp\n",[75,1497,1498,1503],{"__ignoreMap":259},[263,1499,1500],{"class":265,"line":266},[263,1501,1502],{"class":269},"# Docker Compose: DATABASE_URL=postgres:\u002F\u002Fdb:5432\u002Fmyapp\n",[263,1504,1505],{"class":265,"line":273},[263,1506,1507],{"class":269},"# Kubernetes:     DATABASE_URL=postgres:\u002F\u002Fdb-service.default.svc.cluster.local:5432\u002Fmyapp\n",[25,1509,1511],{"id":1510},"phase-5-testing-and-gradual-rollout","Phase 5: Testing and Gradual Rollout",[11,1513,1514],{},"The final migration phase involves staged rollouts and validation.",[182,1516,1518],{"id":1517},"migration-testing-strategy","Migration Testing Strategy",[1520,1521,1522,1528,1544,1550],"ol",{},[190,1523,1524,1527],{},[193,1525,1526],{},"Staging environment validation",": Full functional testing before production migration",[190,1529,1530,1533,1534,748,1539],{},[193,1531,1532],{},"Load testing",": Validate under production-equivalent load using ",[17,1535,1538],{"href":1536,"rel":1537},"https:\u002F\u002Fk6.io\u002F",[21],"k6",[17,1540,1543],{"href":1541,"rel":1542},"https:\u002F\u002Flocust.io\u002F",[21],"Locust",[190,1545,1546,1549],{},[193,1547,1548],{},"Canary deployment",": Route a portion of traffic to the new environment for gradual migration",[190,1551,1552,1555],{},[193,1553,1554],{},"Rollback plan",": Prepare procedures to immediately revert to the old environment if issues arise",[254,1557,1559],{"className":256,"code":1558,"language":258,"meta":259,"style":259},"# Canary deployment example\napiVersion: apps\u002Fv1\nkind: Deployment\nmetadata:\n  name: web-canary\nspec:\n  replicas: 1  # Only a portion of traffic\n  selector:\n    matchLabels:\n      app: web\n      track: canary\n",[75,1560,1561,1566,1575,1584,1590,1599,1605,1618,1624,1630,1638],{"__ignoreMap":259},[263,1562,1563],{"class":265,"line":266},[263,1564,1565],{"class":269},"# Canary deployment example\n",[263,1567,1568,1570,1572],{"class":265,"line":273},[263,1569,1215],{"class":276},[263,1571,298],{"class":280},[263,1573,1574],{"class":301}," apps\u002Fv1\n",[263,1576,1577,1579,1581],{"class":265,"line":284},[263,1578,1226],{"class":276},[263,1580,298],{"class":280},[263,1582,1583],{"class":301}," Deployment\n",[263,1585,1586,1588],{"class":265,"line":292},[263,1587,1237],{"class":276},[263,1589,281],{"class":280},[263,1591,1592,1594,1596],{"class":265,"line":305},[263,1593,1245],{"class":276},[263,1595,298],{"class":280},[263,1597,1598],{"class":301}," web-canary\n",[263,1600,1601,1603],{"class":265,"line":313},[263,1602,1256],{"class":276},[263,1604,281],{"class":280},[263,1606,1607,1610,1612,1615],{"class":265,"line":329},[263,1608,1609],{"class":276},"  replicas",[263,1611,298],{"class":280},[263,1613,1614],{"class":853}," 1",[263,1616,1617],{"class":269},"  # Only a portion of traffic\n",[263,1619,1620,1622],{"class":265,"line":337},[263,1621,1275],{"class":276},[263,1623,281],{"class":280},[263,1625,1626,1628],{"class":265,"line":345},[263,1627,1283],{"class":276},[263,1629,281],{"class":280},[263,1631,1632,1634,1636],{"class":265,"line":353},[263,1633,1291],{"class":276},[263,1635,298],{"class":280},[263,1637,1296],{"class":301},[263,1639,1640,1643,1645],{"class":265,"line":361},[263,1641,1642],{"class":276},"      track",[263,1644,298],{"class":280},[263,1646,1647],{"class":301}," canary\n",[11,1649,1650,1651,1654,1655,1660],{},"For medium-sized applications, plan ",[193,1652,1653],{},"2-4 weeks"," for the complete migration. The ",[17,1656,1659],{"href":1657,"rel":1658},"https:\u002F\u002Fdasroot.net\u002Fposts\u002F2026\u002F03\u002Fmigrating-docker-compose-kubernetes-helm-kustomize\u002F",[21],"dasroot migration guide"," recommends a similar timeline.",[25,1662,1664],{"id":1663},"summary-a-planned-path-to-production-kubernetes","Summary: A Planned Path to Production Kubernetes",[11,1666,1667],{},"Migrate from Docker Compose to Kubernetes through these five planned phases:",[1520,1669,1670,1676,1682,1688,1694],{},[190,1671,1672,1675],{},[193,1673,1674],{},"Assessment",": Inventory Docker Compose configuration and map dependencies",[190,1677,1678,1681],{},[193,1679,1680],{},"Automated Conversion",": Generate initial manifests with Kompose",[190,1683,1684,1687],{},[193,1685,1686],{},"Production-Ready",": Add Probes, resource limits, and Ingress with Helm\u002FKustomize",[190,1689,1690,1693],{},[193,1691,1692],{},"Storage & Network Migration",": Configure PVCs, Secrets, and Services",[190,1695,1696,1699],{},[193,1697,1698],{},"Gradual Rollout",": Staging validation, canary deployments, and load testing",[11,1701,1702,1705,1706,1709],{},[17,1703,22],{"href":19,"rel":1704},[21]," provides the ideal Kubernetes infrastructure as a migration target from Docker Compose. With ",[17,1707,172],{"href":170,"rel":1708},[21],"'s AI assistance, the entire migration process is supported — from Kubernetes manifest generation to operations automation.",[11,1711,1712,1713,639],{},"If you're considering a migration to Kubernetes, please ",[17,1714,1717],{"href":1715,"rel":1716},"https:\u002F\u002Fwww.hexabase.com\u002Fcontact-us\u002F",[21],"contact us",[1719,1720,1721],"style",{},"html pre.shiki code .sbD-w, html code.shiki .sbD-w{--shiki-default:#51597D;--shiki-default-font-style:italic}html pre.shiki code .s0U2E, html code.shiki .s0U2E{--shiki-default:#F7768E}html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .sPY7s, html code.shiki .sPY7s{--shiki-default:#9ECE6A}html pre.shiki code .sgJMe, html code.shiki .sgJMe{--shiki-default:#9ABDF5}html pre.shiki code .sGX4V, html code.shiki .sGX4V{--shiki-default:#A9B1D6}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 .sE3pS, html code.shiki .sE3pS{--shiki-default:#C0CAF5}html pre.shiki code .sT800, html code.shiki .sT800{--shiki-default:#E0AF68}html pre.shiki code .sOJ5S, html code.shiki .sOJ5S{--shiki-default:#FF9E64}",{"title":259,"searchDepth":273,"depth":273,"links":1723},[1724,1725,1728,1733,1737,1742,1745],{"id":27,"depth":273,"text":28},{"id":176,"depth":273,"text":177,"children":1726},[1727],{"id":184,"depth":284,"text":185},{"id":479,"depth":273,"text":480,"children":1729},[1730,1731,1732],{"id":491,"depth":284,"text":492},{"id":571,"depth":284,"text":572},{"id":642,"depth":284,"text":643},{"id":738,"depth":273,"text":739,"children":1734},[1735,1736],{"id":756,"depth":284,"text":757},{"id":1024,"depth":284,"text":1025},{"id":1306,"depth":273,"text":1307,"children":1738},[1739,1740,1741],{"id":1310,"depth":284,"text":1311},{"id":1416,"depth":284,"text":1417},{"id":1489,"depth":284,"text":100},{"id":1510,"depth":273,"text":1511,"children":1743},[1744],{"id":1517,"depth":284,"text":1518},{"id":1663,"depth":273,"text":1664},"2026-05-27","A step-by-step guide for migrating Docker Compose applications to Kubernetes. Covers Kompose conversion, Helm\u002FKustomize production-readiness, storage, networking, and rollout strategies.","md","en",{},"\u002Fblog\u002Fen\u002Fdocker-compose-to-kubernetes-migration",{"title":5,"description":1747},"blog\u002Fen\u002Fdocker-compose-to-kubernetes-migration",[1755,1756,1757,587,772,1758,1759],"docker-compose","kubernetes","migration","kustomize","container-orchestration","AJ2fX6LH94jSpW3qU0bnq-wH6mlNEfknWJkQzhkF12g",[1762,1771,1780,1792,1801,1811],{"path":1763,"title":1764,"description":1765,"date":1746,"tags":1766},"\u002Fblog\u002Fen\u002Fgithub-actions-kubernetes-cicd","Building a Kubernetes ci-cd Pipeline with GitHub Actions","Learn how to build a Kubernetes ci-cd pipeline with GitHub Actions. Covers Helm deployments, ARC auto-scaling, security scanning, and production best practices.",[1767,1756,1768,772,1769,1770],"github-actions","ci-cd","containers","automation",{"path":1772,"title":1773,"description":1774,"date":1746,"tags":1775},"\u002Fblog\u002Fen\u002Fhelm-charts-best-practices","Helm Chart Development Best Practices 2025","A comprehensive guide to Helm chart development best practices in 2025. Covers template design, testing strategies, OCI registries, security, and ci-cd integration.",[772,1756,1776,1777,1778,1779],"package-management","chart-development","best-practices","gitops",{"path":1781,"title":1782,"description":1783,"date":1784,"tags":1785},"\u002Fblog\u002Fen\u002Fk3s-local-path-provisioner-capacity-node-affinity-pvc-pending","Your K3s Pod Won't Leave Pending. The 'Ignored Capacity' and Node Affinity Trap in Local Storage","A K3s PVC stuck in Pending often traces back to one fact: local-path-provisioner never enforces capacity limits. This post digs into hypervisor thin provisioning, Volume Node Affinity Conflicts, and the backup misconceptions hiding in K3s local storage design.","2026-09-12",[1786,1756,1787,1788,1789,1790,1791],"k3s","storage","local-path-provisioner","persistent-volume","node-affinity","zfs",{"path":1793,"title":1794,"description":1795,"date":1796,"tags":1797},"\u002Fblog\u002Fen\u002Fk3s-keda-vs-hpa-event-driven-autoscaling-decision","KEDA vs HPA on K3s: A Decision Framework for Event-Driven Autoscaling","When CPU\u002Fmemory-based HPA on K3s can't keep up with event-driven workloads, should you extend HPA or adopt KEDA, the CNCF-graduated project? Here's a 3-point decision framework, plus what to watch for on K3s.","2026-09-10",[1786,1756,1798,1799,1800],"hpa","keda","autoscaling",{"path":1802,"title":1803,"description":1804,"date":1805,"tags":1806},"\u002Fblog\u002Fen\u002Fk3s-proxmox-live-migration-etcd-quorum-affinity-design","Proxmox Live Migration Quietly Breaks K3s etcd Quorum: When Virtualization Collides with Raft","A deep dive into how live-migrating a K3s control plane VM on Proxmox can break etcd quorum, explained through heartbeat\u002Felection-timeout mechanics and Proxmox affinity design, with concrete operational countermeasures.","2026-09-09",[1786,1756,1807,1808,1809,1810],"proxmox","etcd","live-migration","high-availability",{"path":1812,"title":1813,"description":1814,"date":1815,"tags":1816},"\u002Fblog\u002Fen\u002Fk3s-harbor-private-registry-docker-hub-rate-limit","Our CI Died at Midnight from Docker Hub's Pull Rate Limit. Designing a Harbor Private Registry for K3s\u002FKubo","Ever had a K3s cluster blocked by Docker Hub's pull rate limit? Here's how a Harbor private registry with Proxy Cache dodges the limit, plus vulnerability scanning, signing, and production-grade operational design.","2026-09-08",[1786,1756,1817,1818,1819],"harbor","container-registry","private-registry",1792488897809]