Jinsi ya kutumia Docker Compose kwenye VPS
Jifunze kuweka Docker Engine na Compose v2 kwenye Ubuntu 24.04. Jua jinsi ya kuunda faili ya compose.yml na kuepuka matatizo ya ufw na backup za volume.
Unachojenga
Docker Compose ndio msingi wa karibu kila kitu kingine kwenye tovuti hii. Nextcloud, Vaultwarden, n8n, Immich, Rocket.Chat — mwongozo wa kila mmoja kati ya hayo huanza na "andika faili hii ya compose", na huu ndio ukurasa unaoelezea maana halisi ya faili hiyo. Utaweka Docker Engine na plugin ya Compose v2 kutoka kwenye repository ya apt ya Docker kwenye Ubuntu 24.04, kisha utaunda stack halisi yenye huduma mbili — Miniflux, msomaji mdogo wa RSS, pamoja na PostgreSQL — kwa sababu mchanganyiko huu unatumia kila mfumo unaotumiwa na programu kubwa: picha zilizofungwa (pinned images), database yenye healthcheck, volume yenye jina, siri (secrets) kwenye faili ya .env, na port iliyochapishwa kwenye localhost pekee.
Usakinishaji huchukua dakika tano. Sehemu nyingine ya mwongozo huu inahusu mambo yanayoweza kuleta matatizo baadaye: kikundi cha docker kuwa root kwa jina lingine, port zilizochapishwa kupita moja kwa moja sheria za ufw, na flag moja kwenye docker compose down inayofuta database yako bila ombi la uthibitisho.
Mahitaji muhimu: Ubuntu 24.04 KVM VPS mpya, mtumiaji mwenye haki za sudo, na gigabyte 1 ya RAM au zaidi. Usakinishaji wa Docker uliopo pia ni sawa — sehemu ya kwanza inaelezea nini cha kufuta.
Sakinisha kutoka repo ya Docker, siyo ya Ubuntu
Lazima uepuke makosa mawili kabla ya kutumia amri ya kwanza. Pakiti ya docker.io ya Ubuntu inafanya kazi, lakini toleo lake ni la zamani kuliko la Docker na halina mpangilio wa plugin unaohitajika. Pia, binary ya docker-compose iliyo tetepe — ile yenye alama ya hyphen — ni Compose v1: inatumia Python na ilifikia mwisho wa mzunguko wa maisha (end-of-life) tangu 2023; ndiyo sababu mafunzo ya zamani yanashindwa. Compose ya sasa ni docker compose yenye nafasi, ni plugin ya CLI, na inasakinishwa kutoka repository sawa na engine.
Ikiwa sehemu yoyote ya hizo tayari ipo kwenye seva, ifute kwanza — ikiwemo docker-compose-v2, ambayo ni packaging ya Ubuntu ya plugin hiyo, ili kila kitu kitoke kwenye repository moja:
sudo apt remove -y docker.io docker-compose docker-compose-v2 docker-doc podman-docker containerd runcPackage 'docker.io' is not installed, so not removed ni matokeo ya kawaida kwenye VPS mpya. Kisha ongeza repository ya Docker na usakinisha:
sudo apt update
sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-pluginHakiki tabaka zote tatu:
docker --version
docker compose version
sudo docker run --rm hello-worldZile mbili za kwanza zinaonyesha strings za toleo — Docker Compose version v2.x.x inathibitisha kuwa una plugin, na siyo binary ya v1 iliyochomoka. Amri ya hello-world inapaswa kuishia na Hello from Docker!. Pakiti hiyo inawasha huduma wakati wa kuwaka (boot); systemctl is-enabled docker inaonyesha enabled.
kikundi cha docker ni root — fanya maamuzi kwa umakini
Kwa sasa kila amri ya docker inahitaji sudo, kwa sababu socket ya daemon kwenye /var/run/docker.sock inamilikiwa na root na kikundi cha docker. Bila kuwa mwanachama, utapata kosa la Docker linalotafutwa zaidi:
permission denied while trying to connect to the Docker daemon socket at
unix:///var/run/docker.sockSuluhisho la kawaida:
sudo usermod -aG docker $USERUanachama wa kikundi huanza baada ya kuingia (login), hivyo kosa litaendelea kwenye shell yako ya sasa. Run newgrp docker kwa ajili ya session hii, au toka na uingie tena; id inapaswa kuorodhesha docker kwenye vikundi vyako.
Sasa sehemu ya ukweli, ikiwa imeelezwa wazi: uanachama wa kikundi cha docker ni root kwenye host. Sio "kama root", sio "iliyoongezewa nguvu" — ni root. Mtu yeyote aliye kwenye kikundi hicho anaweza kuendesha docker run --rm -it -v /:/host alpine chroot /host na kumiliki mfumo mzima wa faili (filesystem), bila kuulizwa nywila. Kikundi hiki kipo kwa ajili ya urahisi, si kwa ajili ya ulinzi (containment).
Njia mbadala ya kweli ni docker rootless mode — daemon yenyewe huendeshwa kama mtumiaji wako asiye na mamlaka. Inakugharimu: bandari (ports) chini ya 1024 zinahitaji mipangilio ya ziada, mtandao hupitia shim ya userspace yenye mzigo wa ziada, na baadhi ya picha (images) hufanya kazi vibaya bila root halisi. Kwenye VPS yenye admin mmoja ambapo mtumiaji pekee anayelingia tayari anamiliki sudo, mabadiliko ya kikundi hayabadili chochote kiutendaji, na ndivyo mwongozo wowote hapa unavyochukulia — usitoe ruhusa hii kama vile ni kitu kidogo kuliko sudo.
Anatomy of a compose file
Weka kila stack kwenye directory yake — jina la directory linakuwa jina la mradi, ambalo huweka viambishi kwenye container, networks, na volumes:
sudo mkdir -p /opt/miniflux && sudo chown $USER /opt/miniflux && cd /opt/minifluxTengeneza compose.yml (jina la kisasa; docker-compose.yml bado inafanya kazi). Acha kutumia key ya zamani ya version: — imepitwa na wakati na Compose itatoa onyo ikiiona.
services:
miniflux:
image: miniflux/miniflux:2.2.9
restart: unless-stopped
ports:
- "127.0.0.1:8080:8080"
environment:
- DATABASE_URL=postgres://miniflux:${POSTGRES_PASSWORD}@db/miniflux?sslmode=disable
- RUN_MIGRATIONS=1
- CREATE_ADMIN=1
- ADMIN_USERNAME=admin
- ADMIN_PASSWORD=${ADMIN_PASSWORD}
depends_on:
db:
condition: service_healthy
db:
image: postgres:16-alpine
restart: unless-stopped
environment:
- POSTGRES_USER=miniflux
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=miniflux
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD", "pg_isready", "-U", "miniflux", "-d", "miniflux"]
interval: 10s
timeout: 5s
retries: 5
volumes:
db-data:Kila mstari hapo juu ni uamuzi. Yachukulie moja baada ya jingine.
Pin image versions — :latest plus a pull is an unattended upgrade
postgres:16-alpine, siyo postgres:latest. Tag haijafungwa: :latest inatafuta tena kile ambacho mtunza programu (maintainer) amepakia hivi karibuni kila unapofanya pull. Unganisha hiyo na tabia ya kufanya upgrade mara kwa mara unayojifunza hapa — docker compose pull && docker compose up -d — na :latest inamaanisha mabadiliko makubwa ya toleo (major-version jumps) yatatokea wakati yanapowasilishwa na upstream, si wakati unachochagua wewe. Kwa PostgreSQL hii siyo nadharia: mabadiliko ya ghafla kutoka 16 hadi 17 yataacha container ikizunguka kwenye kosa (crash-looping) kutokana na directory ya data isiyoendana, kwa sababu PostgreSQL inahitaji dump na restore kwa major upgrades, siyo restart tu.
Funga angalau toleo kuu (postgres:16-alpine hufuata matoleo ya patch ya 16.x), na ufunge programu kwenye toleo kamili kama miniflux/miniflux:2.2.9 — kagua ukurasa wa matoleo wa mradi na utumie lililo la sasa unapoandika file. Kisha upgrade inakuwa mabadiliko ya mstari mmoja uliyofanya kwa makusudi, unaoonekana kwenye git diff.
Publish to 127.0.0.1, because Docker walks around ufw
"127.0.0.1:8080:8080" — anwani ya host, port ya host, port ya container. Tutorial nyingi huandika "8080:8080", ambayo ni kifupi cha 0.0.0.0:8080:8080: kusikiliza kwenye kila interface, ikiwemo ile ya umma.
Hapa ndipo penye mtego, na unawagonga karibu kila mtu mara moja. Docker huchapisha port kwa kuandika sheria ya DNAT inayobadilisha lengo la paketi (destination IP) kuwa IP ya ndani ya container kabla ya kuchuja, hivyo paketi hutumia njia ya FORWARD na haigusi kamwe INPUT, ambapo sheria zako za ufw zipo. sudo ufw deny 8080 itaonyesha mafanikio, ufw status itaonyesha port imekataliwa, lakini huduma bado inajibu mtandao wote. Firewall yako haijaharibika; inapingwa kwa usanifu. Kwa nini Docker inapita ufw, na jinsi ya kuchuja trafiki ya container kwa uhakika inaelezea mfumo huo na suluhisho la DOCKER-USER kwa port zinazopaswa kubaki za umma.
Tabia inayofanya tatizo hili litokee: funga (bind) port zinazochapishwa kwenye 127.0.0.1 isipokuwa kama una sababu maalum ya kutofanya hivyo, na weka reverse proxy mbele kwa kitu chochote kinachopaswa kukutana na ulimwengu. Hicho ndicho hasa Mwongozo wa reverse proxy wa Traefik unachojenga kama hatua inayofuata baada ya ukurasa huu — container moja inayomiliki port 80 na 443 na inayopitisha (route) kila kitu kingine kwa hostname, ikiwa na TLS. (Unatoka kwenye usanidi wa zamani wa Traefik v2? Mwongozo wa uhamiaji wa Traefik v2 kwenda v3 unashughulikia mabadiliko ya majina na sheria.)
Hakiki ufungaji (bind) baada ya kuanza stack: sudo ss -tlnp | grep 8080 inapaswa kuonyesha 127.0.0.1:8080, siyo 0.0.0.0:8080 au *:8080.
Named volumes vs bind mounts
db-data:/var/lib/postgresql/data ni named volume: Docker hutengeneza na kusimamia directory chini ya /var/lib/docker/volumes/ na kuiunganisha (mount) ndani ya container. Njia mbadala ni bind mount, ./data:/var/lib/postgresql/data, ambayo huunganisha njia (path) uliyochagua kwenye host.
Mgawanyo unaofanya kazi vizuri: named volumes kwa container za data pekee — kanzidata (databases) zaidi yote, kwa sababu Docker huandaa volume kwa umiliki unaotakiwa na image na ruhusa za faili zinafanya kazi vizuri. Bind mounts kwa faili unazozigusa kutoka kwenye host — faili za usanidi unazozihariri kwa programu ya maandishi, maktaba ya media unayoweka kwa rsync, kitu chochote ambacho unataka njia yake iwe wazi. Kosa la kawaida la bind-mount ni umiliki: container inafanya kazi kama UID 999, directory yako ya host inamilikiwa na UID 1000, na programu inakufa wakati wa kuanza na permission denied kwenye logs zake. Named volumes hufanya aina hiyo ya hitilafu kutoweka kabisa, kwa gharama ya data kuishi kwenye njia inayosimamiwa na Docker — inayofafanuliwa hapa chini.
environment and .env — keep secrets out of git
${POSTGRES_PASSWORD} haisomwi kutoka kwenye shell yako; Compose inatafsiri kutoka kwenye file linaitwa .env lililopo kando ya compose.yml. Litengeneze:
cat > .env <<'EOF'
POSTGRES_PASSWORD=change-me-to-something-long
ADMIN_PASSWORD=change-me-too
EOF
chmod 600 .env
echo ".env" >> .gitignoreTengeneza thamani halisi kwa openssl rand -hex 24. Hex, siyo base64, kwa makusudi: nywila hii inaingia ndani ya mstari wa muunganisho wa DATABASE_URL, na wahusika wa /, +, na = wanaotolewa na base64 yanavuruga uchambuzi wa URL — hitilafu inayojitokeza kama kosa la uthibitisho (authentication error), siyo kosa la sintaksia, na inapoteza muda wako. Mstari wa .gitignore unawekwa kabla ya commit ya kwanza: file la compose liko salama kuchapishwa na kuwekwa kwenye mfumo wa toleo (version control), lakini file la .env haliko salama, na siri ambayo imeshawahi kuingia kwenye historia ya git ni siri ambayo unapaswa kuibadilisha (rotate). Ukianza stack ikiwa na variable iliyokosekana, Compose itatoa onyo kubwa na kuendelea na mstari tupu — ambayo kwa nywila ya Postgres inamaanisha deployment iliyofeli:
WARN[0000] The "POSTGRES_PASSWORD" variable is not set. Defaulting to a blank string.docker compose config huchapa file lililotafsiriwa kikamilifu — njia ya haraka zaidi ya kuangalia kile ambacho container zitapokea; kumbuka matokeo yake yanajumuisha siri zako.
depends_on waits for nothing — unless you add a healthcheck
depends_on: [db] pekee inadhibiti mpangilio wa kuanza tu: Compose huanzisha Postgres kwanza na programu baada ya muda mfupi, wakati Postgres bado ni sekunde chache mbali na kukubali miunganisho. Programu inajaribu kuungana na database, inafeli, na inazima au kujaribu tena kulingana na jinsi ilivyoandikwa.
Toleo linaloaminika ni lile linalotumika kwenye file hapo juu: huduma ya db inafafanua healthcheck (Postgres inakuja na pg_isready kwa ajili hiyo), na programu inadai depends_on kwa kutumia condition: service_healthy. Compose huanzisha database, hukagua hali (check) kila baada ya sekunde 10, na huanza Miniflux tu baada ya ukaguzi kukamilika. Ikiwa database haitakuwa na afya — nywila mbaya, volume iliyoharibika — programu haitaanza na Compose itakuambia ni dependency gani imefeli:
dependency failed to start: container miniflux-db-1 is unhealthyUjumbe huo unakupeleka kwenye docker compose logs db, ambapo kosa halisi lipo.
restart: unless-stopped
restart: unless-stopped kwenye huduma zote mbili inamaanisha container zinarudi baada ya kosa na baada ya VPS kuwashwa upya, lakini zinabaki zimezima ikiwa uliacha kwa makusudi kwa kutumia docker compose stop. Njia mbadala always huamsha container hata baada ya kuzima kwa mkono — mara nyingi siyo kile ulichokusudia. Bila sera ya kuanza upya (restart policy), kuwashwa upya kwa kernel-update saa 4 asubuhi kutazima huduma zako kimya kimya hadi utakapozigundua.
Amri za kila siku
Kila kitu cha kila siku kinahusisha amri tano, ambazo hufadhiwa kwenye directory ya mradi.
docker compose up -d # create and start; idempotent, recreates only what changed
docker compose ps # status, ports, and health of this project's containers
docker compose logs -f miniflux # follow one service's logs; --tail 100 for recent history
docker compose pull && docker compose up -d # upgrade to the pinned tags
docker compose down # stop and remove containers and the networkup -d ni salama kutumika mara nyingi — inalinganisha faili na hali halisi na kubadilisha huduma ambazo konfigirete au picha (image) zimebadilika tu. Amri ya upgrade hupata kile ambacho tag zako zimeelekezwa sasa: matoleo ya patch chini ya postgres:16-alpine, hakuna kitu kwa pin sahihi hadi uibadilishe — ndio lengo la mchakato huu. Picha za zamani hukusanyika baada ya upgrade; rudisha nafasi ya diski kwa kutumia docker image prune -f.
Sasa amri inayofuta data: docker compose down ni salama — kani (containers) na mtandao vinaweza kufutwa, na data yako iko kwenye volume. docker compose down -v hufuta pia volume zilizopewa majina. Hiyo ni database yako, itapotea papo hapo, bila ombi la uthibitisho na bila uwezo wa kurudisha nyuma. Flag ya -v ipo kwa ajili ya kufuta majaribio; kwenye stack yenye data halisi, itumie kama unavyotumia rm -rf. Hakuna pipa la takataka chini ya /var/lib/docker/volumes/.
Kwa shell ya muda mrefu ndani ya container inayojiendesha: docker compose exec db psql -U miniflux inakupeleka kwenye database, na docker compose exec miniflux sh inakupa shell kwenye app.
Mahali ambapo data yako inahifadhiwa
Named volumes hupata kiambishi cha mradi, hivyo db-data kwenye directory inayoitwa miniflux inakuwa miniflux_db-data:
docker volume ls
docker volume inspect miniflux_db-dataOutput ya inspect inajumuisha mstari muhimu:
"Mountpoint": "/var/lib/docker/volumes/miniflux_db-data/_data"Directory hiyo ndiyo database — inamilikiwa na root, kwenye filesystem ya host, na hudumu baada ya down, upgrades, na kuunda upya container. Pia, ndiyo sehemu ambayo nakala zako za ziada (backups) lazima zikakamate.
Backup volume iliyopewa jina
Njia ya kawaida ni kutumia container ya muda inayofunga volume hiyo kwa hali ya kusoma tu (read-only) karibu na directory ya host, kisha kutumia tar:
docker run --rm \
-v miniflux_db-data:/data:ro \
-v "$PWD":/backup \
alpine:3.22 tar czf /backup/miniflux-db-$(date +%F).tar.gz -C /data .Hakuna programu inayohitaji kusakinishwa, hakuna kitu kinachoendelea kuendesha, na urejeshaji ni picha sawa — tar xzf kwenda kwenye volume mpya tupu yenye mounts zilezile ikiwa imebadilishwa.
Angalizo moja kwa database: kutumia tar kwenye directory ya data ya Postgres inayojiendesha kunaweza kunasa hali ya uandishi inayozingua, hali itakayofanya isianze vizuri. Tumia docker compose stop kwa sekunde chache tar inachukua, au — bora zaidi — chukua logical dump, ambayo ni thabiti kwa muundo wake:
docker compose exec -T db pg_dump -U miniflux miniflux | gzip > miniflux-$(date +%F).sql.gz-T huondoa pseudo-terminal ambayo Compose hutenga kwa kawaida — kupeleka output ya dump kupitia TTY kunaweza kuiharibu. Weka mojawapo ya hizi kwenye cron na nakili matokeo nje ya VPS; backup kwenye diski ileile iliyo na data inayolindwa ni nakala tu, siyo backup. Mwongozo wa Nextcloud unajenga utaratibu kamili wa kupanga muda unaozingatia mifumo hii miwili:
Njia za kushindili, pamoja na maandishi utakayoyaona
permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock — bado haupo kwenye kikundi cha docker, au upo lakini kikao chako kilianza kabla ya kuongezwa. id inaonyesha vikundi vyako vilivyopo; newgrp docker inafanya marekebisho kwenye shell ya sasa, kutoa akaunti na kuingia tena inafanya marekebisho kwa vyote.
Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running? — tatizo tofauti: daemon imezimika. sudo systemctl status docker na sudo journalctl -u docker -n 50 vinaelezea sababu. Kwenye VPS, sababu ya kawaida ni diski iliyojaa — angalia df -h /var/lib/docker kwanza.
Bind for 127.0.0.1:8080 failed: port is already allocated — kontena nyingine tayari imetumia port hiyo ya host. docker ps inaonyesha ipi; kontena ya zamani kutoka docker run ya wiki kadhaa zilizopita ndiyo chanzo cha kawaida. Ikiwa docker ps ni safi, mchakato usio wa Docker unashikilia port: sudo ss -tlnp | grep 8080 unataja mchakato huo.
yaml: line 14: did not find expected key — kosa la mpangilio (indentation) kwenye au juu ya mstari uliotajwa. Compose files ni YAML: tumia nafasi mbili (two-space indentation), tumia nafasi pekee, na alama ya tab (tab character) mahali popote itasababisha hitilafu. docker compose config inahakiki faili bila kuanza kitu chochote, na kuikimbiza baada ya kila marekebisho ni tabia nzuri.
Ukejaji wa ufw haitoi kosa lolote, jambo ambalo hufanya iwe hatari: deployment inafanya kazi, ufw status inaonekana sawa, lakini ukipitia port (port scan) kutoka nje utapata database yako. Soma tena sehemu ya ports hapo juu, kagua kila ingizo ya ports: ili kuona kama imekosa kiambishi cha 127.0.0.1:, na uthibitishe kutoka mashine nyingine kwa kutumia curl http://your-vps-ip:8080 — "connection refused" ndiyo jibu unalotaka.
Kutoka hapa, mwongozo wa Traefik unabadilisha stack hii kuwa programu nyingi nyuma ya kiingilizi kimoja cha HTTPS, na vitu vya kujihostia wenyewe mwaka 2026 ni orodha ya vitu vya kuendesha kupitia mfumo huo.
Server ya mchezo kama server ya Minecraft kwenye VPS ni mradi mzuri wa kwanza wa Compose kwa ajili ya mazoezi.
FAQ
Why do I get "permission denied while trying to connect to the Docker daemon socket"?
Your user is not in the docker group, or was added after the current session began — membership only applies at login. Run sudo usermod -aG docker $USER, then newgrp docker or log out and back in, and confirm with id. The group grants root-equivalent access to the host, so only add users you would give sudo.
Does docker compose down delete my data?
Plain docker compose down does not — it removes containers and the project network; named volumes survive and the next up -d reattaches them. docker compose down -v is the destructive form: it deletes the named volumes, meaning your database, with no confirmation and no undo. Never run -v on a stack with real data unless you hold a verified backup.
What is the difference between docker-compose and docker compose?
docker-compose (hyphen) is Compose v1, a standalone Python binary that reached end of life in 2023 and should not be installed on new servers. docker compose (space) is Compose v2, a Go plugin for the Docker CLI, installed as docker-compose-plugin from Docker's apt repository. Commands and YAML are almost fully compatible, so when an old tutorial says docker-compose up, type docker compose up.
Why can I reach my Docker container from the internet even though ufw blocks the port?
Because Docker publishes ports with DNAT rules in iptables' PREROUTING chain, and the rewritten packets travel the FORWARD path through Docker's own chains — they never hit the INPUT chain where ufw's rules apply. ufw deny 8080 therefore does nothing to a published container port. Fix it at the source: publish to 127.0.0.1: and expose services through a reverse proxy instead.
Should I use a named volume or a bind mount?
Named volumes for data only the container touches — databases especially, since Docker sets the ownership the image expects and permissions just work. Bind mounts for files you also handle from the host: configs you edit, media you upload, anything whose path you want obvious. If a container fails at startup with permission denied on a bind mount, host-vs-container UID mismatch is the first thing to check.