[{"data":1,"prerenderedAt":1524},["ShallowReactive",2],{"page-en-\u002Fblog\u002Fdocker-compose-guide":3},{"id":4,"title":5,"body":6,"date":1512,"description":1513,"extension":1514,"meta":1515,"navigation":235,"path":1516,"seo":1517,"stem":1518,"tags":1519,"translation":1522,"__hash__":1523},"pages_en\u002Fen\u002Fblog\u002Fdocker-compose-guide.md","Running Multiple Services with Docker Compose",{"type":7,"value":8,"toc":1501},"minimark",[9,14,26,29,51,55,486,489,561,565,578,613,620,651,667,670,690,694,921,989,993,1003,1087,1093,1119,1123,1126,1214,1251,1255,1325,1382,1386,1484,1488,1497],[10,11,13],"h2",{"id":12},"introduction","Introduction",[15,16,17,18,22,23,25],"p",{},"Running a single container with ",[19,20,21],"code",{},"docker run"," is easy; but a real application usually consists of an API, a database, a cache and maybe a reverse proxy. Starting each of them with separate ",[19,24,21],{}," commands and the right network and volume settings is tedious and error-prone.",[15,27,28],{},"Docker Compose lets you define all of these services in one YAML file and manage them with a single command. This post walks through a Spring Boot API + PostgreSQL + Redis example.",[30,31,32],"blockquote",{},[15,33,34,35,38,39,42,43,46,47,50],{},"Compose is now part of the Docker CLI: the command is ",[19,36,37],{},"docker compose",", not ",[19,40,41],{},"docker-compose",". The recommended file name is ",[19,44,45],{},"compose.yaml","; ",[19,48,49],{},"docker-compose.yml"," still works.",[10,52,54],{"id":53},"_1-a-first-composeyaml","1. A first compose.yaml",[56,57,62],"pre",{"className":58,"code":59,"language":60,"meta":61,"style":61},"language-yaml shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","services:\n  api:\n    build: .\n    ports:\n      - \"8080:8080\"\n    environment:\n      SPRING_DATASOURCE_URL: jdbc:postgresql:\u002F\u002Fdb:5432\u002Fapp\n      SPRING_DATASOURCE_USERNAME: app\n      SPRING_DATASOURCE_PASSWORD: ${DB_PASSWORD}\n      SPRING_DATA_REDIS_HOST: cache\n    depends_on:\n      db:\n        condition: service_healthy\n      cache:\n        condition: service_started\n    restart: unless-stopped\n\n  db:\n    image: postgres:17\n    environment:\n      POSTGRES_DB: app\n      POSTGRES_USER: app\n      POSTGRES_PASSWORD: ${DB_PASSWORD}\n    volumes:\n      - pgdata:\u002Fvar\u002Flib\u002Fpostgresql\u002Fdata\n    healthcheck:\n      test: [\"CMD-SHELL\", \"pg_isready -U app -d app\"]\n      interval: 5s\n      timeout: 3s\n      retries: 10\n\n  cache:\n    image: redis:7-alpine\n    command: [\"redis-server\", \"--appendonly\", \"yes\"]\n    volumes:\n      - redisdata:\u002Fdata\n\nvolumes:\n  pgdata:\n  redisdata:\n","yaml","",[19,63,64,77,85,98,106,122,130,141,152,163,174,182,190,201,209,219,230,237,245,256,263,273,283,293,301,309,317,349,360,371,382,387,395,405,442,449,457,462,470,478],{"__ignoreMap":61},[65,66,69,73],"span",{"class":67,"line":68},"line",1,[65,70,72],{"class":71},"swJcz","services",[65,74,76],{"class":75},"sMK4o",":\n",[65,78,80,83],{"class":67,"line":79},2,[65,81,82],{"class":71},"  api",[65,84,76],{"class":75},[65,86,88,91,94],{"class":67,"line":87},3,[65,89,90],{"class":71},"    build",[65,92,93],{"class":75},":",[65,95,97],{"class":96},"sbssI"," .\n",[65,99,101,104],{"class":67,"line":100},4,[65,102,103],{"class":71},"    ports",[65,105,76],{"class":75},[65,107,109,112,115,119],{"class":67,"line":108},5,[65,110,111],{"class":75},"      -",[65,113,114],{"class":75}," \"",[65,116,118],{"class":117},"sfazB","8080:8080",[65,120,121],{"class":75},"\"\n",[65,123,125,128],{"class":67,"line":124},6,[65,126,127],{"class":71},"    environment",[65,129,76],{"class":75},[65,131,133,136,138],{"class":67,"line":132},7,[65,134,135],{"class":71},"      SPRING_DATASOURCE_URL",[65,137,93],{"class":75},[65,139,140],{"class":117}," jdbc:postgresql:\u002F\u002Fdb:5432\u002Fapp\n",[65,142,144,147,149],{"class":67,"line":143},8,[65,145,146],{"class":71},"      SPRING_DATASOURCE_USERNAME",[65,148,93],{"class":75},[65,150,151],{"class":117}," app\n",[65,153,155,158,160],{"class":67,"line":154},9,[65,156,157],{"class":71},"      SPRING_DATASOURCE_PASSWORD",[65,159,93],{"class":75},[65,161,162],{"class":117}," ${DB_PASSWORD}\n",[65,164,166,169,171],{"class":67,"line":165},10,[65,167,168],{"class":71},"      SPRING_DATA_REDIS_HOST",[65,170,93],{"class":75},[65,172,173],{"class":117}," cache\n",[65,175,177,180],{"class":67,"line":176},11,[65,178,179],{"class":71},"    depends_on",[65,181,76],{"class":75},[65,183,185,188],{"class":67,"line":184},12,[65,186,187],{"class":71},"      db",[65,189,76],{"class":75},[65,191,193,196,198],{"class":67,"line":192},13,[65,194,195],{"class":71},"        condition",[65,197,93],{"class":75},[65,199,200],{"class":117}," service_healthy\n",[65,202,204,207],{"class":67,"line":203},14,[65,205,206],{"class":71},"      cache",[65,208,76],{"class":75},[65,210,212,214,216],{"class":67,"line":211},15,[65,213,195],{"class":71},[65,215,93],{"class":75},[65,217,218],{"class":117}," service_started\n",[65,220,222,225,227],{"class":67,"line":221},16,[65,223,224],{"class":71},"    restart",[65,226,93],{"class":75},[65,228,229],{"class":117}," unless-stopped\n",[65,231,233],{"class":67,"line":232},17,[65,234,236],{"emptyLinePlaceholder":235},true,"\n",[65,238,240,243],{"class":67,"line":239},18,[65,241,242],{"class":71},"  db",[65,244,76],{"class":75},[65,246,248,251,253],{"class":67,"line":247},19,[65,249,250],{"class":71},"    image",[65,252,93],{"class":75},[65,254,255],{"class":117}," postgres:17\n",[65,257,259,261],{"class":67,"line":258},20,[65,260,127],{"class":71},[65,262,76],{"class":75},[65,264,266,269,271],{"class":67,"line":265},21,[65,267,268],{"class":71},"      POSTGRES_DB",[65,270,93],{"class":75},[65,272,151],{"class":117},[65,274,276,279,281],{"class":67,"line":275},22,[65,277,278],{"class":71},"      POSTGRES_USER",[65,280,93],{"class":75},[65,282,151],{"class":117},[65,284,286,289,291],{"class":67,"line":285},23,[65,287,288],{"class":71},"      POSTGRES_PASSWORD",[65,290,93],{"class":75},[65,292,162],{"class":117},[65,294,296,299],{"class":67,"line":295},24,[65,297,298],{"class":71},"    volumes",[65,300,76],{"class":75},[65,302,304,306],{"class":67,"line":303},25,[65,305,111],{"class":75},[65,307,308],{"class":117}," pgdata:\u002Fvar\u002Flib\u002Fpostgresql\u002Fdata\n",[65,310,312,315],{"class":67,"line":311},26,[65,313,314],{"class":71},"    healthcheck",[65,316,76],{"class":75},[65,318,320,323,325,328,331,334,336,339,341,344,346],{"class":67,"line":319},27,[65,321,322],{"class":71},"      test",[65,324,93],{"class":75},[65,326,327],{"class":75}," [",[65,329,330],{"class":75},"\"",[65,332,333],{"class":117},"CMD-SHELL",[65,335,330],{"class":75},[65,337,338],{"class":75},",",[65,340,114],{"class":75},[65,342,343],{"class":117},"pg_isready -U app -d app",[65,345,330],{"class":75},[65,347,348],{"class":75},"]\n",[65,350,352,355,357],{"class":67,"line":351},28,[65,353,354],{"class":71},"      interval",[65,356,93],{"class":75},[65,358,359],{"class":117}," 5s\n",[65,361,363,366,368],{"class":67,"line":362},29,[65,364,365],{"class":71},"      timeout",[65,367,93],{"class":75},[65,369,370],{"class":117}," 3s\n",[65,372,374,377,379],{"class":67,"line":373},30,[65,375,376],{"class":71},"      retries",[65,378,93],{"class":75},[65,380,381],{"class":96}," 10\n",[65,383,385],{"class":67,"line":384},31,[65,386,236],{"emptyLinePlaceholder":235},[65,388,390,393],{"class":67,"line":389},32,[65,391,392],{"class":71},"  cache",[65,394,76],{"class":75},[65,396,398,400,402],{"class":67,"line":397},33,[65,399,250],{"class":71},[65,401,93],{"class":75},[65,403,404],{"class":117}," redis:7-alpine\n",[65,406,408,411,413,415,417,420,422,424,426,429,431,433,435,438,440],{"class":67,"line":407},34,[65,409,410],{"class":71},"    command",[65,412,93],{"class":75},[65,414,327],{"class":75},[65,416,330],{"class":75},[65,418,419],{"class":117},"redis-server",[65,421,330],{"class":75},[65,423,338],{"class":75},[65,425,114],{"class":75},[65,427,428],{"class":117},"--appendonly",[65,430,330],{"class":75},[65,432,338],{"class":75},[65,434,114],{"class":75},[65,436,437],{"class":117},"yes",[65,439,330],{"class":75},[65,441,348],{"class":75},[65,443,445,447],{"class":67,"line":444},35,[65,446,298],{"class":71},[65,448,76],{"class":75},[65,450,452,454],{"class":67,"line":451},36,[65,453,111],{"class":75},[65,455,456],{"class":117}," redisdata:\u002Fdata\n",[65,458,460],{"class":67,"line":459},37,[65,461,236],{"emptyLinePlaceholder":235},[65,463,465,468],{"class":67,"line":464},38,[65,466,467],{"class":71},"volumes",[65,469,76],{"class":75},[65,471,473,476],{"class":67,"line":472},39,[65,474,475],{"class":71},"  pgdata",[65,477,76],{"class":75},[65,479,481,484],{"class":67,"line":480},40,[65,482,483],{"class":71},"  redisdata",[65,485,76],{"class":75},[15,487,488],{},"A few things to note:",[490,491,492,511,527,543],"ul",{},[493,494,495,499,500,503,504,38,507,510],"li",{},[496,497,498],"strong",{},"Service names are DNS names."," The ",[19,501,502],{},"api"," container reaches the database at ",[19,505,506],{},"db",[19,508,509],{},"localhost",". Compose puts all services on a shared network.",[493,512,513,519,520,522,523,526],{},[496,514,515,518],{},[19,516,517],{},"ports"," is only needed for services exposed to the outside."," ",[19,521,506],{}," and ",[19,524,525],{},"cache"," publish no host ports, so they're reachable only from inside the compose network.",[493,528,529,538,539,542],{},[496,530,531,534,535],{},[19,532,533],{},"depends_on"," + ",[19,536,537],{},"condition: service_healthy"," makes the API wait until the database actually accepts connections. Plain ",[19,540,541],{},"depends_on: [db]"," only waits for the container to start, not to be ready.",[493,544,545,548,549,552,553,556,557,560],{},[496,546,547],{},"Named volumes"," (",[19,550,551],{},"pgdata",", ",[19,554,555],{},"redisdata",") survive ",[19,558,559],{},"docker compose down","; your data isn't lost.",[10,562,564],{"id":563},"_2-environment-variables-and-env","2. Environment variables and .env",[15,566,567,568,571,572,575,576,93],{},"Variables like ",[19,569,570],{},"${DB_PASSWORD}"," are read from a ",[19,573,574],{},".env"," file next to ",[19,577,45],{},[56,579,583],{"className":580,"code":581,"language":582,"meta":61,"style":61},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","# .env\nDB_PASSWORD=local-dev-password\nCOMPOSE_PROJECT_NAME=myapp\n","bash",[19,584,585,591,603],{"__ignoreMap":61},[65,586,587],{"class":67,"line":68},[65,588,590],{"class":589},"sHwdD","# .env\n",[65,592,593,597,600],{"class":67,"line":79},[65,594,596],{"class":595},"sTEyZ","DB_PASSWORD",[65,598,599],{"class":75},"=",[65,601,602],{"class":117},"local-dev-password\n",[65,604,605,608,610],{"class":67,"line":87},[65,606,607],{"class":595},"COMPOSE_PROJECT_NAME",[65,609,599],{"class":75},[65,611,612],{"class":117},"myapp\n",[15,614,615,616,619],{},"To pass many variables into a container, ",[19,617,618],{},"env_file"," is cleaner:",[56,621,623],{"className":58,"code":622,"language":60,"meta":61,"style":61},"services:\n  api:\n    env_file:\n      - .env.api\n",[19,624,625,631,637,644],{"__ignoreMap":61},[65,626,627,629],{"class":67,"line":68},[65,628,72],{"class":71},[65,630,76],{"class":75},[65,632,633,635],{"class":67,"line":79},[65,634,82],{"class":71},[65,636,76],{"class":75},[65,638,639,642],{"class":67,"line":87},[65,640,641],{"class":71},"    env_file",[65,643,76],{"class":75},[65,645,646,648],{"class":67,"line":100},[65,647,111],{"class":75},[65,649,650],{"class":117}," .env.api\n",[30,652,653],{},[15,654,655,656,658,659,662,663,666],{},"Add ",[19,657,574],{}," files to ",[19,660,661],{},".gitignore"," and commit a ",[19,664,665],{},".env.example"," instead.",[15,668,669],{},"To see how Compose resolves the variables:",[56,671,673],{"className":580,"code":672,"language":582,"meta":61,"style":61},"docker compose config        # prints the final file with variables substituted\n",[19,674,675],{"__ignoreMap":61},[65,676,677,681,684,687],{"class":67,"line":68},[65,678,680],{"class":679},"sBMFI","docker",[65,682,683],{"class":117}," compose",[65,685,686],{"class":117}," config",[65,688,689],{"class":589},"        # prints the final file with variables substituted\n",[10,691,693],{"id":692},"_3-everyday-commands","3. Everyday commands",[56,695,697],{"className":580,"code":696,"language":582,"meta":61,"style":61},"docker compose up -d                 # start all services in the background\ndocker compose up -d --build         # rebuild images, then start\ndocker compose up -d api             # only api (and its dependencies)\ndocker compose ps                    # service status\ndocker compose logs -f api           # follow api logs\ndocker compose logs --tail=100       # last 100 lines of every service\ndocker compose exec db psql -U app   # run a command in a running container\ndocker compose run --rm api sh       # one-off new container\ndocker compose restart api\ndocker compose stop                  # stop, keep containers\ndocker compose down                  # stop and remove containers + network\ndocker compose down -v               # remove volumes too (data is lost!)\ndocker compose pull                  # pull newer image versions\ndocker compose build --no-cache api\ndocker compose top                   # processes in the containers\n",[19,698,699,714,730,746,758,775,789,813,833,845,857,869,883,895,909],{"__ignoreMap":61},[65,700,701,703,705,708,711],{"class":67,"line":68},[65,702,680],{"class":679},[65,704,683],{"class":117},[65,706,707],{"class":117}," up",[65,709,710],{"class":117}," -d",[65,712,713],{"class":589},"                 # start all services in the background\n",[65,715,716,718,720,722,724,727],{"class":67,"line":79},[65,717,680],{"class":679},[65,719,683],{"class":117},[65,721,707],{"class":117},[65,723,710],{"class":117},[65,725,726],{"class":117}," --build",[65,728,729],{"class":589},"         # rebuild images, then start\n",[65,731,732,734,736,738,740,743],{"class":67,"line":87},[65,733,680],{"class":679},[65,735,683],{"class":117},[65,737,707],{"class":117},[65,739,710],{"class":117},[65,741,742],{"class":117}," api",[65,744,745],{"class":589},"             # only api (and its dependencies)\n",[65,747,748,750,752,755],{"class":67,"line":100},[65,749,680],{"class":679},[65,751,683],{"class":117},[65,753,754],{"class":117}," ps",[65,756,757],{"class":589},"                    # service status\n",[65,759,760,762,764,767,770,772],{"class":67,"line":108},[65,761,680],{"class":679},[65,763,683],{"class":117},[65,765,766],{"class":117}," logs",[65,768,769],{"class":117}," -f",[65,771,742],{"class":117},[65,773,774],{"class":589},"           # follow api logs\n",[65,776,777,779,781,783,786],{"class":67,"line":124},[65,778,680],{"class":679},[65,780,683],{"class":117},[65,782,766],{"class":117},[65,784,785],{"class":117}," --tail=100",[65,787,788],{"class":589},"       # last 100 lines of every service\n",[65,790,791,793,795,798,801,804,807,810],{"class":67,"line":132},[65,792,680],{"class":679},[65,794,683],{"class":117},[65,796,797],{"class":117}," exec",[65,799,800],{"class":117}," db",[65,802,803],{"class":117}," psql",[65,805,806],{"class":117}," -U",[65,808,809],{"class":117}," app",[65,811,812],{"class":589},"   # run a command in a running container\n",[65,814,815,817,819,822,825,827,830],{"class":67,"line":143},[65,816,680],{"class":679},[65,818,683],{"class":117},[65,820,821],{"class":117}," run",[65,823,824],{"class":117}," --rm",[65,826,742],{"class":117},[65,828,829],{"class":117}," sh",[65,831,832],{"class":589},"       # one-off new container\n",[65,834,835,837,839,842],{"class":67,"line":154},[65,836,680],{"class":679},[65,838,683],{"class":117},[65,840,841],{"class":117}," restart",[65,843,844],{"class":117}," api\n",[65,846,847,849,851,854],{"class":67,"line":165},[65,848,680],{"class":679},[65,850,683],{"class":117},[65,852,853],{"class":117}," stop",[65,855,856],{"class":589},"                  # stop, keep containers\n",[65,858,859,861,863,866],{"class":67,"line":176},[65,860,680],{"class":679},[65,862,683],{"class":117},[65,864,865],{"class":117}," down",[65,867,868],{"class":589},"                  # stop and remove containers + network\n",[65,870,871,873,875,877,880],{"class":67,"line":184},[65,872,680],{"class":679},[65,874,683],{"class":117},[65,876,865],{"class":117},[65,878,879],{"class":117}," -v",[65,881,882],{"class":589},"               # remove volumes too (data is lost!)\n",[65,884,885,887,889,892],{"class":67,"line":192},[65,886,680],{"class":679},[65,888,683],{"class":117},[65,890,891],{"class":117}," pull",[65,893,894],{"class":589},"                  # pull newer image versions\n",[65,896,897,899,901,904,907],{"class":67,"line":203},[65,898,680],{"class":679},[65,900,683],{"class":117},[65,902,903],{"class":117}," build",[65,905,906],{"class":117}," --no-cache",[65,908,844],{"class":117},[65,910,911,913,915,918],{"class":67,"line":211},[65,912,680],{"class":679},[65,914,683],{"class":117},[65,916,917],{"class":117}," top",[65,919,920],{"class":589},"                   # processes in the containers\n",[922,923,924,943],"table",{},[925,926,927],"thead",{},[928,929,930,934,937,940],"tr",{},[931,932,933],"th",{},"Command",[931,935,936],{},"Containers",[931,938,939],{},"Network",[931,941,942],{},"Volumes",[944,945,946,962,976],"tbody",{},[928,947,948,954,957,960],{},[949,950,951],"td",{},[19,952,953],{},"stop",[949,955,956],{},"Stopped, kept",[949,958,959],{},"Kept",[949,961,959],{},[928,963,964,969,972,974],{},[949,965,966],{},[19,967,968],{},"down",[949,970,971],{},"Removed",[949,973,971],{},[949,975,959],{},[928,977,978,983,985,987],{},[949,979,980],{},[19,981,982],{},"down -v",[949,984,971],{},[949,986,971],{},[949,988,971],{},[10,990,992],{"id":991},"_4-overrides-for-development","4. Overrides for development",[15,994,995,996,999,1000,1002],{},"Compose automatically reads ",[19,997,998],{},"compose.override.yaml"," alongside ",[19,1001,45],{},". Putting development-only settings there keeps the main file close to production:",[56,1004,1006],{"className":58,"code":1005,"language":60,"meta":61,"style":61},"# compose.override.yaml\nservices:\n  api:\n    environment:\n      SPRING_PROFILES_ACTIVE: dev\n    ports:\n      - \"5005:5005\"     # remote debug\n  db:\n    ports:\n      - \"5432:5432\"     # to connect with DBeaver etc.\n",[19,1007,1008,1013,1019,1025,1031,1041,1047,1061,1067,1073],{"__ignoreMap":61},[65,1009,1010],{"class":67,"line":68},[65,1011,1012],{"class":589},"# compose.override.yaml\n",[65,1014,1015,1017],{"class":67,"line":79},[65,1016,72],{"class":71},[65,1018,76],{"class":75},[65,1020,1021,1023],{"class":67,"line":87},[65,1022,82],{"class":71},[65,1024,76],{"class":75},[65,1026,1027,1029],{"class":67,"line":100},[65,1028,127],{"class":71},[65,1030,76],{"class":75},[65,1032,1033,1036,1038],{"class":67,"line":108},[65,1034,1035],{"class":71},"      SPRING_PROFILES_ACTIVE",[65,1037,93],{"class":75},[65,1039,1040],{"class":117}," dev\n",[65,1042,1043,1045],{"class":67,"line":124},[65,1044,103],{"class":71},[65,1046,76],{"class":75},[65,1048,1049,1051,1053,1056,1058],{"class":67,"line":132},[65,1050,111],{"class":75},[65,1052,114],{"class":75},[65,1054,1055],{"class":117},"5005:5005",[65,1057,330],{"class":75},[65,1059,1060],{"class":589},"     # remote debug\n",[65,1062,1063,1065],{"class":67,"line":143},[65,1064,242],{"class":71},[65,1066,76],{"class":75},[65,1068,1069,1071],{"class":67,"line":154},[65,1070,103],{"class":71},[65,1072,76],{"class":75},[65,1074,1075,1077,1079,1082,1084],{"class":67,"line":165},[65,1076,111],{"class":75},[65,1078,114],{"class":75},[65,1080,1081],{"class":117},"5432:5432",[65,1083,330],{"class":75},[65,1085,1086],{"class":589},"     # to connect with DBeaver etc.\n",[15,1088,1089,1090,93],{},"To use a different file in production, list the files explicitly with ",[19,1091,1092],{},"-f",[56,1094,1096],{"className":580,"code":1095,"language":582,"meta":61,"style":61},"docker compose -f compose.yaml -f compose.prod.yaml up -d\n",[19,1097,1098],{"__ignoreMap":61},[65,1099,1100,1102,1104,1106,1109,1111,1114,1116],{"class":67,"line":68},[65,1101,680],{"class":679},[65,1103,683],{"class":117},[65,1105,769],{"class":117},[65,1107,1108],{"class":117}," compose.yaml",[65,1110,769],{"class":117},[65,1112,1113],{"class":117}," compose.prod.yaml",[65,1115,707],{"class":117},[65,1117,1118],{"class":117}," -d\n",[10,1120,1122],{"id":1121},"_5-profiles","5. Profiles",[15,1124,1125],{},"Services that don't always need to run (e.g. pgAdmin, a mail test server) can be tied to a profile:",[56,1127,1129],{"className":58,"code":1128,"language":60,"meta":61,"style":61},"services:\n  pgadmin:\n    image: dpage\u002Fpgadmin4\n    profiles: [\"tools\"]\n    ports:\n      - \"5050:80\"\n    environment:\n      PGADMIN_DEFAULT_EMAIL: admin@example.com\n      PGADMIN_DEFAULT_PASSWORD: admin\n",[19,1130,1131,1137,1144,1153,1171,1177,1188,1194,1204],{"__ignoreMap":61},[65,1132,1133,1135],{"class":67,"line":68},[65,1134,72],{"class":71},[65,1136,76],{"class":75},[65,1138,1139,1142],{"class":67,"line":79},[65,1140,1141],{"class":71},"  pgadmin",[65,1143,76],{"class":75},[65,1145,1146,1148,1150],{"class":67,"line":87},[65,1147,250],{"class":71},[65,1149,93],{"class":75},[65,1151,1152],{"class":117}," dpage\u002Fpgadmin4\n",[65,1154,1155,1158,1160,1162,1164,1167,1169],{"class":67,"line":100},[65,1156,1157],{"class":71},"    profiles",[65,1159,93],{"class":75},[65,1161,327],{"class":75},[65,1163,330],{"class":75},[65,1165,1166],{"class":117},"tools",[65,1168,330],{"class":75},[65,1170,348],{"class":75},[65,1172,1173,1175],{"class":67,"line":108},[65,1174,103],{"class":71},[65,1176,76],{"class":75},[65,1178,1179,1181,1183,1186],{"class":67,"line":124},[65,1180,111],{"class":75},[65,1182,114],{"class":75},[65,1184,1185],{"class":117},"5050:80",[65,1187,121],{"class":75},[65,1189,1190,1192],{"class":67,"line":132},[65,1191,127],{"class":71},[65,1193,76],{"class":75},[65,1195,1196,1199,1201],{"class":67,"line":143},[65,1197,1198],{"class":71},"      PGADMIN_DEFAULT_EMAIL",[65,1200,93],{"class":75},[65,1202,1203],{"class":117}," admin@example.com\n",[65,1205,1206,1209,1211],{"class":67,"line":154},[65,1207,1208],{"class":71},"      PGADMIN_DEFAULT_PASSWORD",[65,1210,93],{"class":75},[65,1212,1213],{"class":117}," admin\n",[56,1215,1217],{"className":580,"code":1216,"language":582,"meta":61,"style":61},"docker compose up -d                     # pgadmin doesn't start\ndocker compose --profile tools up -d     # pgadmin starts too\n",[19,1218,1219,1232],{"__ignoreMap":61},[65,1220,1221,1223,1225,1227,1229],{"class":67,"line":68},[65,1222,680],{"class":679},[65,1224,683],{"class":117},[65,1226,707],{"class":117},[65,1228,710],{"class":117},[65,1230,1231],{"class":589},"                     # pgadmin doesn't start\n",[65,1233,1234,1236,1238,1241,1244,1246,1248],{"class":67,"line":79},[65,1235,680],{"class":679},[65,1237,683],{"class":117},[65,1239,1240],{"class":117}," --profile",[65,1242,1243],{"class":117}," tools",[65,1245,707],{"class":117},[65,1247,710],{"class":117},[65,1249,1250],{"class":589},"     # pgadmin starts too\n",[10,1252,1254],{"id":1253},"_6-resource-limits-and-restart-policy","6. Resource limits and restart policy",[56,1256,1258],{"className":58,"code":1257,"language":60,"meta":61,"style":61},"services:\n  api:\n    restart: unless-stopped\n    deploy:\n      resources:\n        limits:\n          cpus: \"1.0\"\n          memory: 512M\n",[19,1259,1260,1266,1272,1280,1287,1294,1301,1315],{"__ignoreMap":61},[65,1261,1262,1264],{"class":67,"line":68},[65,1263,72],{"class":71},[65,1265,76],{"class":75},[65,1267,1268,1270],{"class":67,"line":79},[65,1269,82],{"class":71},[65,1271,76],{"class":75},[65,1273,1274,1276,1278],{"class":67,"line":87},[65,1275,224],{"class":71},[65,1277,93],{"class":75},[65,1279,229],{"class":117},[65,1281,1282,1285],{"class":67,"line":100},[65,1283,1284],{"class":71},"    deploy",[65,1286,76],{"class":75},[65,1288,1289,1292],{"class":67,"line":108},[65,1290,1291],{"class":71},"      resources",[65,1293,76],{"class":75},[65,1295,1296,1299],{"class":67,"line":124},[65,1297,1298],{"class":71},"        limits",[65,1300,76],{"class":75},[65,1302,1303,1306,1308,1310,1313],{"class":67,"line":132},[65,1304,1305],{"class":71},"          cpus",[65,1307,93],{"class":75},[65,1309,114],{"class":75},[65,1311,1312],{"class":117},"1.0",[65,1314,121],{"class":75},[65,1316,1317,1320,1322],{"class":67,"line":143},[65,1318,1319],{"class":71},"          memory",[65,1321,93],{"class":75},[65,1323,1324],{"class":117}," 512M\n",[922,1326,1327,1340],{},[925,1328,1329],{},[928,1330,1331,1337],{},[931,1332,1333,1336],{},[19,1334,1335],{},"restart"," value",[931,1338,1339],{},"Behavior",[944,1341,1342,1352,1362,1372],{},[928,1343,1344,1349],{},[949,1345,1346],{},[19,1347,1348],{},"no",[949,1350,1351],{},"Default, never restarts",[928,1353,1354,1359],{},[949,1355,1356],{},[19,1357,1358],{},"always",[949,1360,1361],{},"Always restarts",[928,1363,1364,1369],{},[949,1365,1366],{},[19,1367,1368],{},"on-failure",[949,1370,1371],{},"Only when it exits with an error code",[928,1373,1374,1379],{},[949,1375,1376],{},[19,1377,1378],{},"unless-stopped",[949,1380,1381],{},"Restarts unless stopped manually",[10,1383,1385],{"id":1384},"_7-troubleshooting","7. Troubleshooting",[922,1387,1388,1398],{},[925,1389,1390],{},[928,1391,1392,1395],{},[931,1393,1394],{},"Problem",[931,1396,1397],{},"Fix",[944,1399,1400,1417,1432,1446,1457,1468],{},[928,1401,1402,1411],{},[949,1403,1404,1407,1408],{},[19,1405,1406],{},"Connection refused"," → ",[19,1409,1410],{},"localhost:5432",[949,1412,1413,1414],{},"From inside a container use the service name: ",[19,1415,1416],{},"db:5432",[928,1418,1419,1422],{},[949,1420,1421],{},"API starts before the database and crashes",[949,1423,655,1424,534,1426,1428,1429],{},[19,1425,533],{},[19,1427,537],{}," and a ",[19,1430,1431],{},"healthcheck",[928,1433,1434,1439],{},[949,1435,1436],{},[19,1437,1438],{},"port is already allocated",[949,1440,1441,1442,1445],{},"Something on the host uses that port; find it with ",[19,1443,1444],{},"ss -ltnp"," or change the mapping",[928,1447,1448,1451],{},[949,1449,1450],{},"Code changes don't reach the container",[949,1452,1453,1454],{},"Rebuild the image with ",[19,1455,1456],{},"docker compose up -d --build",[928,1458,1459,1462],{},[949,1460,1461],{},"Changed the DB password but the old one still works",[949,1463,1464,1465,1467],{},"Postgres writes the password to the volume on first init; remove it with ",[19,1466,982],{}," (data is lost)",[928,1469,1470,1475],{},[949,1471,1472,1474],{},[19,1473,574],{}," variables come through empty",[949,1476,1477,1478,46,1481,1483],{},"Check with ",[19,1479,1480],{},"docker compose config",[19,1482,574],{}," must sit next to the compose file",[10,1485,1487],{"id":1486},"conclusion","Conclusion",[15,1489,1490,1491,1493,1494,1496],{},"Docker Compose is the most practical way to define a multi-service application in one file and let everyone bring up the same environment with one command. Remembering that service names are DNS names, controlling startup order with a ",[19,1492,1431],{},", and knowing that ",[19,1495,982],{}," deletes data prevents most of the problems you'll run into.",[1498,1499,1500],"style",{},"html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}",{"title":61,"searchDepth":79,"depth":79,"links":1502},[1503,1504,1505,1506,1507,1508,1509,1510,1511],{"id":12,"depth":79,"text":13},{"id":53,"depth":79,"text":54},{"id":563,"depth":79,"text":564},{"id":692,"depth":79,"text":693},{"id":991,"depth":79,"text":992},{"id":1121,"depth":79,"text":1122},{"id":1253,"depth":79,"text":1254},{"id":1384,"depth":79,"text":1385},{"id":1486,"depth":79,"text":1487},"2026-10-27","Bringing up an app, a database and a cache from a single compose.yaml — services, networks, volumes, healthchecks, env files and everyday commands.","md",{},"\u002Fen\u002Fblog\u002Fdocker-compose-guide",{"title":5,"description":1513},"en\u002Fblog\u002Fdocker-compose-guide",[680,1520,1521],"devops","guide","\u002Fblog\u002Fdocker-compose-guide","ppscnab4_0LO6QWg3xSiJph5eatnHNi-lqBftjZSkzM",1793060538395]