[{"data":1,"prerenderedAt":1339},["ShallowReactive",2],{"navigation":3,"\u002Fen\u002Fblog\u002Fdocker-compose-for-beginners":4,"\u002Fen\u002Fblog\u002Fdocker-compose-for-beginners-surround":1328},[],{"id":5,"title":6,"authors":7,"badge":13,"body":14,"date":1316,"description":1317,"extension":1318,"image":1319,"lastUpdated":1321,"meta":1322,"navigation":173,"path":1323,"published":173,"seo":1324,"stem":1325,"tags":1326,"__hash__":1327},"posts\u002Fen\u002F3.blog\u002F4.docker-compose-for-beginners.md","Docker Compose Tutorial: Managing Multi-Container Apps Made Easy",[8],{"name":9,"to":10,"avatar":11},"Fabian Sander","\u002Fabout\u002Ffabiansander",{"src":12},"\u002Fimages\u002Fblog\u002Fauthors\u002Ffabian.png",null,{"type":15,"value":16,"toc":1300},"minimark",[17,22,32,35,48,56,61,68,75,80,88,92,95,100,115,119,126,130,133,136,375,385,389,392,397,416,423,428,440,443,448,463,466,471,483,486,491,508,511,515,526,536,563,568,587,591,594,603,609,626,632,636,639,891,894,1010,1014,1019,1033,1036,1052,1058,1062,1108,1112,1115,1213,1217,1220,1225,1251,1254,1259,1288,1291,1296],[18,19,21],"h1",{"id":20},"docker-compose-tutorial-for-beginners","Docker Compose Tutorial for Beginners",[23,24,25,26,31],"p",{},"In our ",[27,28,30],"a",{"href":29},"\u002Fen\u002Fblog\u002Fhow-docker-works","last article",", we looked at how Docker works under the hood. We learned what images are, how the daemon operates, and how to start a single container.",[23,33,34],{},"But let's be honest: Modern web applications are rarely solo acts.",[23,36,37,38,42,43,47],{},"A typical app often consists of a frontend, a backend service, a database like ",[27,39,41],{"href":40},"\u002Fen\u002Fblog\u002Fpostgresql-helm-chart-kubernetes","PostgreSQL",", and maybe a caching layer like Redis. If you try to juggle all of this with individual ",[44,45,46],"code",{},"docker run"," commands in the terminal, you'll quickly lose track. You'd have to manually create networks, manage IP addresses, and pay attention to startup order.",[23,49,50,51,55],{},"This is exactly where ",[52,53,54],"strong",{},"Docker Compose"," comes into play.",[57,58,60],"h2",{"id":59},"what-is-docker-compose","What is Docker Compose?",[23,62,63,64,67],{},"Docker Compose is a tool for multi-container applications. If the ",[44,65,66],{},"Dockerfile"," is the recipe for a single component (like a cake), then Docker Compose is the menu for the entire 3-course meal.",[23,69,70,71,74],{},"With a single file, the ",[44,72,73],{},"docker-compose.yml",", you describe your complete infrastructure. The genius part: You can spin up your entire environment with a single command.",[23,76,77],{},[52,78,79],{},"Why is this especially important for selfhosting?",[23,81,82,83,87],{},"If you want to host your own services on a VPS, you need a solution that manages multiple containers simultaneously. Without Docker Compose, things get messy fast. With Docker Compose, you have everything under control. Whether you're running Nextcloud, GitLab, ",[27,84,86],{"href":85},"\u002Fen\u002Fblog\u002Fself-hosted-n8n-on-hetzner","n8n",", or a WordPress instance.",[57,89,91],{"id":90},"the-anatomy-of-docker-composeyml","The Anatomy of docker-compose.yml",[23,93,94],{},"Docker Compose uses YAML. This is a format that's readable for both humans and machines. Let's look at the most important building blocks.",[96,97,99],"h3",{"id":98},"services-the-individual-containers","Services: The Individual Containers",[23,101,102,103,106,107,110,111,114],{},"Under ",[44,104,105],{},"services"," you define the containers that should run. Each service gets a name (like ",[44,108,109],{},"web"," or ",[44,112,113],{},"db","), which you can later use for internal communication.",[96,116,118],{"id":117},"networks-simple-networking","Networks: Simple Networking",[23,120,121,122,125],{},"Container networking used to be complicated. With Docker Compose, it's almost magical. Services in the same network can reach each other via their service name. You don't have to hardcode IP addresses anymore. Your backend simply calls ",[44,123,124],{},"db:5432",", and Docker routes it to the database container.",[96,127,129],{"id":128},"volumes-persistent-data","Volumes: Persistent Data",[23,131,132],{},"Containers are ephemeral. When you delete a database container, the data is gone. Unless you use volumes. In the Compose file, you define where data should be persistently stored on your host system.",[23,134,135],{},"Here's a practical example for a setup with a web app and database:",[137,138,143],"pre",{"className":139,"code":140,"language":141,"meta":142,"style":142},"language-yaml shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","version: '3.8'\n\nservices:\n  webapp:\n    build: .\u002Fapp\n    ports:\n      - '8080:80'\n    depends_on:\n      - database\n    environment:\n      - DB_HOST=database\n      - DB_USER=${DB_USER}\n      - DB_PASS=${DB_PASSWORD}\n    restart: unless-stopped\n\n  database:\n    image: postgres:15-alpine\n    volumes:\n      - db_data:\u002Fvar\u002Flib\u002Fpostgresql\u002Fdata\n    environment:\n      - POSTGRES_USER=${DB_USER}\n      - POSTGRES_PASSWORD=${DB_PASSWORD}\n    restart: unless-stopped\n\nvolumes:\n  db_data:\n","yaml","",[44,144,145,168,175,183,191,202,210,223,231,239,247,255,263,271,282,287,295,306,314,322,329,337,345,354,359,367],{"__ignoreMap":142},[146,147,150,154,158,161,165],"span",{"class":148,"line":149},"line",1,[146,151,153],{"class":152},"swJcz","version",[146,155,157],{"class":156},"sMK4o",":",[146,159,160],{"class":156}," '",[146,162,164],{"class":163},"sfazB","3.8",[146,166,167],{"class":156},"'\n",[146,169,171],{"class":148,"line":170},2,[146,172,174],{"emptyLinePlaceholder":173},true,"\n",[146,176,178,180],{"class":148,"line":177},3,[146,179,105],{"class":152},[146,181,182],{"class":156},":\n",[146,184,186,189],{"class":148,"line":185},4,[146,187,188],{"class":152},"  webapp",[146,190,182],{"class":156},[146,192,194,197,199],{"class":148,"line":193},5,[146,195,196],{"class":152},"    build",[146,198,157],{"class":156},[146,200,201],{"class":163}," .\u002Fapp\n",[146,203,205,208],{"class":148,"line":204},6,[146,206,207],{"class":152},"    ports",[146,209,182],{"class":156},[146,211,213,216,218,221],{"class":148,"line":212},7,[146,214,215],{"class":156},"      -",[146,217,160],{"class":156},[146,219,220],{"class":163},"8080:80",[146,222,167],{"class":156},[146,224,226,229],{"class":148,"line":225},8,[146,227,228],{"class":152},"    depends_on",[146,230,182],{"class":156},[146,232,234,236],{"class":148,"line":233},9,[146,235,215],{"class":156},[146,237,238],{"class":163}," database\n",[146,240,242,245],{"class":148,"line":241},10,[146,243,244],{"class":152},"    environment",[146,246,182],{"class":156},[146,248,250,252],{"class":148,"line":249},11,[146,251,215],{"class":156},[146,253,254],{"class":163}," DB_HOST=database\n",[146,256,258,260],{"class":148,"line":257},12,[146,259,215],{"class":156},[146,261,262],{"class":163}," DB_USER=${DB_USER}\n",[146,264,266,268],{"class":148,"line":265},13,[146,267,215],{"class":156},[146,269,270],{"class":163}," DB_PASS=${DB_PASSWORD}\n",[146,272,274,277,279],{"class":148,"line":273},14,[146,275,276],{"class":152},"    restart",[146,278,157],{"class":156},[146,280,281],{"class":163}," unless-stopped\n",[146,283,285],{"class":148,"line":284},15,[146,286,174],{"emptyLinePlaceholder":173},[146,288,290,293],{"class":148,"line":289},16,[146,291,292],{"class":152},"  database",[146,294,182],{"class":156},[146,296,298,301,303],{"class":148,"line":297},17,[146,299,300],{"class":152},"    image",[146,302,157],{"class":156},[146,304,305],{"class":163}," postgres:15-alpine\n",[146,307,309,312],{"class":148,"line":308},18,[146,310,311],{"class":152},"    volumes",[146,313,182],{"class":156},[146,315,317,319],{"class":148,"line":316},19,[146,318,215],{"class":156},[146,320,321],{"class":163}," db_data:\u002Fvar\u002Flib\u002Fpostgresql\u002Fdata\n",[146,323,325,327],{"class":148,"line":324},20,[146,326,244],{"class":152},[146,328,182],{"class":156},[146,330,332,334],{"class":148,"line":331},21,[146,333,215],{"class":156},[146,335,336],{"class":163}," POSTGRES_USER=${DB_USER}\n",[146,338,340,342],{"class":148,"line":339},22,[146,341,215],{"class":156},[146,343,344],{"class":163}," POSTGRES_PASSWORD=${DB_PASSWORD}\n",[146,346,348,350,352],{"class":148,"line":347},23,[146,349,276],{"class":152},[146,351,157],{"class":156},[146,353,281],{"class":163},[146,355,357],{"class":148,"line":356},24,[146,358,174],{"emptyLinePlaceholder":173},[146,360,362,365],{"class":148,"line":361},25,[146,363,364],{"class":152},"volumes",[146,366,182],{"class":156},[146,368,370,373],{"class":148,"line":369},26,[146,371,372],{"class":152},"  db_data",[146,374,182],{"class":156},[23,376,377],{},[378,379,380,381,384],"em",{},"Note: The variables like ",[44,382,383],{},"${DB_USER}"," are environment variables. More on that in a moment.",[57,386,388],{"id":387},"essential-docker-compose-commands","Essential Docker Compose Commands",[23,390,391],{},"Docker Compose makes your workflow extremely efficient. Instead of long shell scripts, you only need a handful of commands:",[23,393,394],{},[52,395,396],{},"Start containers:",[137,398,402],{"className":399,"code":400,"language":401,"meta":142,"style":142},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","docker-compose up -d\n","bash",[44,403,404],{"__ignoreMap":142},[146,405,406,410,413],{"class":148,"line":149},[146,407,409],{"class":408},"sBMFI","docker-compose",[146,411,412],{"class":163}," up",[146,414,415],{"class":163}," -d\n",[23,417,418,419,422],{},"This command reads your configuration, pulls the necessary images, creates networks, and starts all containers in the background (",[44,420,421],{},"-d"," for detached).",[23,424,425],{},[52,426,427],{},"Check status:",[137,429,431],{"className":399,"code":430,"language":401,"meta":142,"style":142},"docker-compose ps\n",[44,432,433],{"__ignoreMap":142},[146,434,435,437],{"class":148,"line":149},[146,436,409],{"class":408},[146,438,439],{"class":163}," ps\n",[23,441,442],{},"Shows you immediately which services are running, which ports are occupied, and if a container has crashed.",[23,444,445],{},[52,446,447],{},"View logs:",[137,449,451],{"className":399,"code":450,"language":401,"meta":142,"style":142},"docker-compose logs -f\n",[44,452,453],{"__ignoreMap":142},[146,454,455,457,460],{"class":148,"line":149},[146,456,409],{"class":408},[146,458,459],{"class":163}," logs",[146,461,462],{"class":163}," -f\n",[23,464,465],{},"Essential for debugging. This lets you see the log outputs of all services bundled in one stream.",[23,467,468],{},[52,469,470],{},"Stop containers:",[137,472,474],{"className":399,"code":473,"language":401,"meta":142,"style":142},"docker-compose down\n",[44,475,476],{"__ignoreMap":142},[146,477,478,480],{"class":148,"line":149},[146,479,409],{"class":408},[146,481,482],{"class":163}," down\n",[23,484,485],{},"Stops the containers and cleans up the networks. Clean and tidy.",[23,487,488],{},[52,489,490],{},"Rebuild containers:",[137,492,494],{"className":399,"code":493,"language":401,"meta":142,"style":142},"docker-compose up -d --build\n",[44,495,496],{"__ignoreMap":142},[146,497,498,500,502,505],{"class":148,"line":149},[146,499,409],{"class":408},[146,501,412],{"class":163},[146,503,504],{"class":163}," -d",[146,506,507],{"class":163}," --build\n",[23,509,510],{},"If you've made changes to your code, this command rebuilds the images and starts the containers.",[57,512,514],{"id":513},"environment-variables-and-security","Environment Variables and Security",[23,516,517,518,521,522,525],{},"In the code example above, we used ",[44,519,520],{},"${DB_PASSWORD}",". Hardcoded passwords in a ",[44,523,524],{},"yml"," file are a security risk – especially if you push the code to GitHub.",[23,527,528,529,532,533,535],{},"Docker Compose supports ",[44,530,531],{},".env"," files automatically. Create a file named ",[44,534,531],{}," in the same folder and add the variables:",[137,537,539],{"className":399,"code":538,"language":401,"meta":142,"style":142},"DB_USER=admin\nDB_PASSWORD=supersecret\n",[44,540,541,553],{"__ignoreMap":142},[146,542,543,547,550],{"class":148,"line":149},[146,544,546],{"class":545},"sTEyZ","DB_USER",[146,548,549],{"class":156},"=",[146,551,552],{"class":163},"admin\n",[146,554,555,558,560],{"class":148,"line":170},[146,556,557],{"class":545},"DB_PASSWORD",[146,559,549],{"class":156},[146,561,562],{"class":163},"supersecret\n",[23,564,565],{},[52,566,567],{},"Important for security:",[569,570,571,581,584],"ul",{},[572,573,574,575,577,578],"li",{},"Add ",[44,576,531],{}," to your ",[44,579,580],{},".gitignore",[572,582,583],{},"Use different passwords on your production server than locally",[572,585,586],{},"Never use default passwords like \"admin123\"",[57,588,590],{"id":589},"docker-compose-for-selfhosting-on-your-vps","Docker Compose for Selfhosting on Your VPS",[23,592,593],{},"Why is Docker Compose perfect for selfhosting?",[23,595,596,599,600,602],{},[52,597,598],{},"1. Easy Setup on a VPS","\nYou log in via SSH to your server, upload your ",[44,601,73],{},", and start your complete infrastructure with one command.",[23,604,605,608],{},[52,606,607],{},"2. Reproducible Environments","\nYour local development looks exactly like your production server. No more \"but it works on my machine!\" problems.",[23,610,611,614,615,617,618,621,622,625],{},[52,612,613],{},"3. Simple Updates","\nNew version of your app? Change the image tag in the ",[44,616,73],{},", run ",[44,619,620],{},"docker-compose pull"," and ",[44,623,624],{},"docker-compose up -d"," – done.",[23,627,628,631],{},[52,629,630],{},"4. Resource Efficiency","\nOn a VPS with 4GB RAM, you can easily host 5-10 smaller services simultaneously, thanks to the lean container architecture.",[57,633,635],{"id":634},"practical-example-wordpress-with-mysql","Practical Example: WordPress with MySQL",[23,637,638],{},"Here's a complete example of how you can selfhost WordPress:",[137,640,642],{"className":139,"code":641,"language":141,"meta":142,"style":142},"version: '3.8'\n\nservices:\n  wordpress:\n    image: wordpress:latest\n    ports:\n      - '80:80'\n    environment:\n      WORDPRESS_DB_HOST: db\n      WORDPRESS_DB_USER: ${DB_USER}\n      WORDPRESS_DB_PASSWORD: ${DB_PASSWORD}\n      WORDPRESS_DB_NAME: wordpress\n    volumes:\n      - wordpress_data:\u002Fvar\u002Fwww\u002Fhtml\n    depends_on:\n      - db\n    restart: unless-stopped\n\n  db:\n    image: mysql:8.0\n    environment:\n      MYSQL_DATABASE: wordpress\n      MYSQL_USER: ${DB_USER}\n      MYSQL_PASSWORD: ${DB_PASSWORD}\n      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}\n    volumes:\n      - db_data:\u002Fvar\u002Flib\u002Fmysql\n    restart: unless-stopped\n\nvolumes:\n  wordpress_data:\n  db_data:\n",[44,643,644,656,660,666,673,682,688,699,705,715,725,735,745,751,758,764,770,778,782,789,798,804,813,822,831,841,847,855,864,869,876,884],{"__ignoreMap":142},[146,645,646,648,650,652,654],{"class":148,"line":149},[146,647,153],{"class":152},[146,649,157],{"class":156},[146,651,160],{"class":156},[146,653,164],{"class":163},[146,655,167],{"class":156},[146,657,658],{"class":148,"line":170},[146,659,174],{"emptyLinePlaceholder":173},[146,661,662,664],{"class":148,"line":177},[146,663,105],{"class":152},[146,665,182],{"class":156},[146,667,668,671],{"class":148,"line":185},[146,669,670],{"class":152},"  wordpress",[146,672,182],{"class":156},[146,674,675,677,679],{"class":148,"line":193},[146,676,300],{"class":152},[146,678,157],{"class":156},[146,680,681],{"class":163}," wordpress:latest\n",[146,683,684,686],{"class":148,"line":204},[146,685,207],{"class":152},[146,687,182],{"class":156},[146,689,690,692,694,697],{"class":148,"line":212},[146,691,215],{"class":156},[146,693,160],{"class":156},[146,695,696],{"class":163},"80:80",[146,698,167],{"class":156},[146,700,701,703],{"class":148,"line":225},[146,702,244],{"class":152},[146,704,182],{"class":156},[146,706,707,710,712],{"class":148,"line":233},[146,708,709],{"class":152},"      WORDPRESS_DB_HOST",[146,711,157],{"class":156},[146,713,714],{"class":163}," db\n",[146,716,717,720,722],{"class":148,"line":241},[146,718,719],{"class":152},"      WORDPRESS_DB_USER",[146,721,157],{"class":156},[146,723,724],{"class":163}," ${DB_USER}\n",[146,726,727,730,732],{"class":148,"line":249},[146,728,729],{"class":152},"      WORDPRESS_DB_PASSWORD",[146,731,157],{"class":156},[146,733,734],{"class":163}," ${DB_PASSWORD}\n",[146,736,737,740,742],{"class":148,"line":257},[146,738,739],{"class":152},"      WORDPRESS_DB_NAME",[146,741,157],{"class":156},[146,743,744],{"class":163}," wordpress\n",[146,746,747,749],{"class":148,"line":265},[146,748,311],{"class":152},[146,750,182],{"class":156},[146,752,753,755],{"class":148,"line":273},[146,754,215],{"class":156},[146,756,757],{"class":163}," wordpress_data:\u002Fvar\u002Fwww\u002Fhtml\n",[146,759,760,762],{"class":148,"line":284},[146,761,228],{"class":152},[146,763,182],{"class":156},[146,765,766,768],{"class":148,"line":289},[146,767,215],{"class":156},[146,769,714],{"class":163},[146,771,772,774,776],{"class":148,"line":297},[146,773,276],{"class":152},[146,775,157],{"class":156},[146,777,281],{"class":163},[146,779,780],{"class":148,"line":308},[146,781,174],{"emptyLinePlaceholder":173},[146,783,784,787],{"class":148,"line":316},[146,785,786],{"class":152},"  db",[146,788,182],{"class":156},[146,790,791,793,795],{"class":148,"line":324},[146,792,300],{"class":152},[146,794,157],{"class":156},[146,796,797],{"class":163}," mysql:8.0\n",[146,799,800,802],{"class":148,"line":331},[146,801,244],{"class":152},[146,803,182],{"class":156},[146,805,806,809,811],{"class":148,"line":339},[146,807,808],{"class":152},"      MYSQL_DATABASE",[146,810,157],{"class":156},[146,812,744],{"class":163},[146,814,815,818,820],{"class":148,"line":347},[146,816,817],{"class":152},"      MYSQL_USER",[146,819,157],{"class":156},[146,821,724],{"class":163},[146,823,824,827,829],{"class":148,"line":356},[146,825,826],{"class":152},"      MYSQL_PASSWORD",[146,828,157],{"class":156},[146,830,734],{"class":163},[146,832,833,836,838],{"class":148,"line":361},[146,834,835],{"class":152},"      MYSQL_ROOT_PASSWORD",[146,837,157],{"class":156},[146,839,840],{"class":163}," ${DB_ROOT_PASSWORD}\n",[146,842,843,845],{"class":148,"line":369},[146,844,311],{"class":152},[146,846,182],{"class":156},[146,848,850,852],{"class":148,"line":849},27,[146,851,215],{"class":156},[146,853,854],{"class":163}," db_data:\u002Fvar\u002Flib\u002Fmysql\n",[146,856,858,860,862],{"class":148,"line":857},28,[146,859,276],{"class":152},[146,861,157],{"class":156},[146,863,281],{"class":163},[146,865,867],{"class":148,"line":866},29,[146,868,174],{"emptyLinePlaceholder":173},[146,870,872,874],{"class":148,"line":871},30,[146,873,364],{"class":152},[146,875,182],{"class":156},[146,877,879,882],{"class":148,"line":878},31,[146,880,881],{"class":152},"  wordpress_data",[146,883,182],{"class":156},[146,885,887,889],{"class":148,"line":886},32,[146,888,372],{"class":152},[146,890,182],{"class":156},[23,892,893],{},"Here's how to start it on your server:",[137,895,897],{"className":399,"code":896,"language":401,"meta":142,"style":142},"# Upload Docker Compose file\nscp docker-compose.yml root@your-server.com:\u002Fopt\u002Fwordpress\u002F\n\n# On the server\nssh root@your-server.com\ncd \u002Fopt\u002Fwordpress\necho \"DB_USER=wpuser\" > .env\necho \"DB_PASSWORD=secure-password\" >> .env\necho \"DB_ROOT_PASSWORD=even-more-secure-password\" >> .env\n\n# Start\ndocker-compose up -d\n",[44,898,899,905,916,920,925,933,942,962,978,993,997,1002],{"__ignoreMap":142},[146,900,901],{"class":148,"line":149},[146,902,904],{"class":903},"sHwdD","# Upload Docker Compose file\n",[146,906,907,910,913],{"class":148,"line":170},[146,908,909],{"class":408},"scp",[146,911,912],{"class":163}," docker-compose.yml",[146,914,915],{"class":163}," root@your-server.com:\u002Fopt\u002Fwordpress\u002F\n",[146,917,918],{"class":148,"line":177},[146,919,174],{"emptyLinePlaceholder":173},[146,921,922],{"class":148,"line":185},[146,923,924],{"class":903},"# On the server\n",[146,926,927,930],{"class":148,"line":193},[146,928,929],{"class":408},"ssh",[146,931,932],{"class":163}," root@your-server.com\n",[146,934,935,939],{"class":148,"line":204},[146,936,938],{"class":937},"s2Zo4","cd",[146,940,941],{"class":163}," \u002Fopt\u002Fwordpress\n",[146,943,944,947,950,953,956,959],{"class":148,"line":212},[146,945,946],{"class":937},"echo",[146,948,949],{"class":156}," \"",[146,951,952],{"class":163},"DB_USER=wpuser",[146,954,955],{"class":156},"\"",[146,957,958],{"class":156}," >",[146,960,961],{"class":163}," .env\n",[146,963,964,966,968,971,973,976],{"class":148,"line":225},[146,965,946],{"class":937},[146,967,949],{"class":156},[146,969,970],{"class":163},"DB_PASSWORD=secure-password",[146,972,955],{"class":156},[146,974,975],{"class":156}," >>",[146,977,961],{"class":163},[146,979,980,982,984,987,989,991],{"class":148,"line":233},[146,981,946],{"class":937},[146,983,949],{"class":156},[146,985,986],{"class":163},"DB_ROOT_PASSWORD=even-more-secure-password",[146,988,955],{"class":156},[146,990,975],{"class":156},[146,992,961],{"class":163},[146,994,995],{"class":148,"line":241},[146,996,174],{"emptyLinePlaceholder":173},[146,998,999],{"class":148,"line":249},[146,1000,1001],{"class":903},"# Start\n",[146,1003,1004,1006,1008],{"class":148,"line":257},[146,1005,409],{"class":408},[146,1007,412],{"class":163},[146,1009,415],{"class":163},[57,1011,1013],{"id":1012},"common-problems-and-solutions","Common Problems and Solutions",[23,1015,1016],{},[52,1017,1018],{},"Problem: Container won't start",[137,1020,1022],{"className":399,"code":1021,"language":401,"meta":142,"style":142},"docker-compose logs service-name\n",[44,1023,1024],{"__ignoreMap":142},[146,1025,1026,1028,1030],{"class":148,"line":149},[146,1027,409],{"class":408},[146,1029,459],{"class":163},[146,1031,1032],{"class":163}," service-name\n",[23,1034,1035],{},"Check the logs. Usually an environment variable is missing or a volume path doesn't exist.",[23,1037,1038,1041,1042,1044,1045,1048,1049],{},[52,1039,1040],{},"Problem: Port already in use","\nChange the port on the left side of the colon in ",[44,1043,73],{},": ",[44,1046,1047],{},"\"8080:80\""," instead of ",[44,1050,1051],{},"\"80:80\"",[23,1053,1054,1057],{},[52,1055,1056],{},"Problem: Container can't access other services","\nCheck if all services are in the same network and if the service names are spelled correctly.",[57,1059,1061],{"id":1060},"best-practices-for-docker-compose","Best Practices for Docker Compose",[1063,1064,1065,1074,1087,1096,1102],"ol",{},[572,1066,1067,1073],{},[52,1068,1069,1070],{},"Always use ",[44,1071,1072],{},"restart: unless-stopped","\nThis makes your containers start automatically after a server reboot",[572,1075,1076,1079,1080,1082,1083,1086],{},[52,1077,1078],{},"Separate development and production","\nUse separate Compose files: ",[44,1081,73],{}," for development, ",[44,1084,1085],{},"docker-compose.prod.yml"," for production",[572,1088,1089,1092,1093,1095],{},[52,1090,1091],{},"Manage secrets securely","\nUse ",[44,1094,531],{}," files for local development and Docker Secrets or environment variables for production",[572,1097,1098,1101],{},[52,1099,1100],{},"Use healthchecks","\nSo Docker automatically checks if your services are actually running",[572,1103,1104,1107],{},[52,1105,1106],{},"Limit resources","\nEspecially important on smaller VPS – prevents one container from consuming all resources",[57,1109,1111],{"id":1110},"from-local-to-production-the-deployment-workflow","From Local to Production: The Deployment Workflow",[23,1113,1114],{},"Here's what a typical workflow looks like:",[1063,1116,1117,1150,1189],{},[572,1118,1119,1122],{},[52,1120,1121],{},"Develop locally",[137,1123,1125],{"className":399,"code":1124,"language":401,"meta":142,"style":142},"docker-compose up -d\n# Make code changes, test\ndocker-compose restart webapp\n",[44,1126,1127,1135,1140],{"__ignoreMap":142},[146,1128,1129,1131,1133],{"class":148,"line":149},[146,1130,409],{"class":408},[146,1132,412],{"class":163},[146,1134,415],{"class":163},[146,1136,1137],{"class":148,"line":170},[146,1138,1139],{"class":903},"# Make code changes, test\n",[146,1141,1142,1144,1147],{"class":148,"line":177},[146,1143,409],{"class":408},[146,1145,1146],{"class":163}," restart",[146,1148,1149],{"class":163}," webapp\n",[572,1151,1152,1155],{},[52,1153,1154],{},"Deploy to server",[137,1156,1158],{"className":399,"code":1157,"language":401,"meta":142,"style":142},"# Push code to server (Git, rsync, scp)\nssh user@server\ndocker-compose pull\ndocker-compose up -d --build\n",[44,1159,1160,1165,1172,1179],{"__ignoreMap":142},[146,1161,1162],{"class":148,"line":149},[146,1163,1164],{"class":903},"# Push code to server (Git, rsync, scp)\n",[146,1166,1167,1169],{"class":148,"line":170},[146,1168,929],{"class":408},[146,1170,1171],{"class":163}," user@server\n",[146,1173,1174,1176],{"class":148,"line":177},[146,1175,409],{"class":408},[146,1177,1178],{"class":163}," pull\n",[146,1180,1181,1183,1185,1187],{"class":148,"line":185},[146,1182,409],{"class":408},[146,1184,412],{"class":163},[146,1186,504],{"class":163},[146,1188,507],{"class":163},[572,1190,1191,1194],{},[52,1192,1193],{},"Monitor",[137,1195,1197],{"className":399,"code":1196,"language":401,"meta":142,"style":142},"docker-compose logs -f\ndocker-compose ps\n",[44,1198,1199,1207],{"__ignoreMap":142},[146,1200,1201,1203,1205],{"class":148,"line":149},[146,1202,409],{"class":408},[146,1204,459],{"class":163},[146,1206,462],{"class":163},[146,1208,1209,1211],{"class":148,"line":170},[146,1210,409],{"class":408},[146,1212,439],{"class":163},[57,1214,1216],{"id":1215},"conclusion-docker-compose-for-selfhosting","Conclusion: Docker Compose for Selfhosting",[23,1218,1219],{},"Docker Compose transforms the chaos of individual containers into a well-organized system. It's the tool that takes you from \"playing around with Docker\" to \"running real production environments.\"",[23,1221,1222],{},[52,1223,1224],{},"The key advantages:",[569,1226,1227,1233,1239,1245],{},[572,1228,1229,1232],{},[52,1230,1231],{},"Everything in one place:"," The entire configuration is in one file",[572,1234,1235,1238],{},[52,1236,1237],{},"Isolated environments:"," You can run multiple projects in parallel on one server",[572,1240,1241,1244],{},[52,1242,1243],{},"Easy networking:"," Containers communicate via DNS names",[572,1246,1247,1250],{},[52,1248,1249],{},"Perfect for selfhosting:"," Ideal for VPS hosting on the provider of your choice",[23,1252,1253],{},"Whether you want to host a blog, a cloud storage solution, or a monitoring system, Docker Compose makes it simple, maintainable, and reproducible.",[23,1255,1256],{},[52,1257,1258],{},"Next steps:",[569,1260,1261,1264,1267,1270],{},[572,1262,1263],{},"Get yourself an affordable VPS",[572,1265,1266],{},"Install Docker and Docker Compose",[572,1268,1269],{},"Start with a simple hobby project",[572,1271,1272,1273,1277,1278,1282,1283,1287],{},"Gradually expand your infrastructure — ",[27,1274,1276],{"href":1275},"\u002Fen\u002Fblog\u002Fwhat-is-docker-swarm","explore what Docker Swarm offers"," when you outgrow Compose, or understand the ",[27,1279,1281],{"href":1280},"\u002Fen\u002Fblog\u002Fkubernetes-vs-docker-swarm","differences between Kubernetes and Docker Swarm",", or see ",[27,1284,1286],{"href":1285},"\u002Fen\u002Fblog\u002Fdocker-vs-kubernetes","Docker, Compose, Swarm, and Kubernetes compared"," side by side",[1289,1290],"hr",{},[23,1292,1293],{},[378,1294,1295],{},"Want to learn more about deployment strategies and selfhosting? Check out our hosting solutions at lowcloud.io – optimized for Docker and modern container workflows.",[1297,1298,1299],"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 .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 .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}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 .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}",{"title":142,"searchDepth":170,"depth":170,"links":1301},[1302,1303,1308,1309,1310,1311,1312,1313,1314,1315],{"id":59,"depth":170,"text":60},{"id":90,"depth":170,"text":91,"children":1304},[1305,1306,1307],{"id":98,"depth":177,"text":99},{"id":117,"depth":177,"text":118},{"id":128,"depth":177,"text":129},{"id":387,"depth":170,"text":388},{"id":513,"depth":170,"text":514},{"id":589,"depth":170,"text":590},{"id":634,"depth":170,"text":635},{"id":1012,"depth":170,"text":1013},{"id":1060,"depth":170,"text":1061},{"id":1110,"depth":170,"text":1111},{"id":1215,"depth":170,"text":1216},"2025-12-16","Learn Docker Compose from scratch - This tutorial explains how to manage multi-container applications with a single YAML file and why Docker Compose is essential for selfhosting.","md",{"src":1320},"\u002Fimages\u002Fblog\u002Fdocker_compose_multi_container_applications.jpeg","2026-04-13",{},"\u002Fen\u002Fblog\u002Fdocker-compose-for-beginners",{"title":6,"description":1317},"en\u002F3.blog\u002F4.docker-compose-for-beginners","Economy, Information Technology","I3QNR9cpxjhiTio06y8apBhS-rtTDcl99G2hxjtTjEs",[1329,1334],{"title":1330,"path":1331,"stem":1332,"description":1333,"children":-1},"EU AI Act Hosting: What Changes for AI Workload Operators","\u002Fen\u002Fblog\u002Feu-ai-act-hosting","en\u002F3.blog\u002F39.eu-ai-act-hosting","The EU AI Act introduces new obligations for AI system operators. What hosting customers need to know about risk classification, logging, and sovereign infrastructure.",{"title":1335,"path":1336,"stem":1337,"description":1338,"children":-1},"Full-Stack Developer Reality: What the Title Actually Means","\u002Fen\u002Fblog\u002Ffull-stack-developer-reality","en\u002F3.blog\u002F40.full-stack-developer-reality","An honest look at what full-stack development means today, where the real problems lie, and how developers can navigate it without burning out.",1776469309263]