Docker Compose build vs image: wat is het verschil?
Begrijp waarom Docker Compose uw Dockerfile wijzigingen negeert op een VPS. Leer het cruciale verschil tussen de image en build keys en hoe u lokale builds correct uitvoert.
Docker Compose build versus image: het korte antwoord
In een Docker Compose-bestand specificeert image: een image om op te halen uit een registry, en vertelt build: aan Compose om er een te bouwen op deze machine vanuit een Dockerfile. Stel alleen image: in en Compose haalt die tag op en voert deze uit. Stel alleen build: in en Compose bouwt de image lokaal, waarbij deze een naam krijgt die is afgeleid van de projectnaam en de servicenaam. Stel beide in en Compose bouwt lokaal, waarna het resultaat wordt getagd met de naam in image:; dit is de manier om een image te bouwen en te pushen onder een door u gekozen naam.
Dat is het volledige onderscheid. Alles hieronder beschrijft wat dit betekent in de praktijk op een server. Dit gaat ervan uit dat Docker Engine en de Compose-plugin al zijn geïnstalleerd; Docker uitvoeren op een VPS behandelt dat onderdeel.
De drie vormen in detail
Haal een gepubliceerde tag op en voer deze uit. Er komt op geen enkel moment een Dockerfile aan te pas.
services:
web:
image: nginx:1.27
restart: unless-stopped
ports:
- "80:80"Bouw vanuit een Dockerfile in de huidige map. Er wordt niets opgehaald, behalve de basis-image die in FROM wordt genoemd.
services:
web:
build: .
restart: unless-stopped
ports:
- "80:80"Bouw lokaal en voorzie het resultaat van een tag. docker compose push kan die specifieke tag vervolgens naar een registry sturen.
services:
web:
build:
context: .
dockerfile: Dockerfile
image: registry.example.com/acme/web:1.4.2
restart: unless-stopped
ports:
- "80:80"context is de map die naar de builder wordt verzonden. dockerfile wordt relatief ten opzichte van die context opgelost, dus context: . met dockerfile: docker/prod.Dockerfile is normaal en correct. Voer docker compose images uit om de image-naam en het image-ID achter elke servicecontainer te zien; dit is de snelste manier om te bevestigen welke van deze drie vormen u daadwerkelijk heeft geschreven.
Waarom voert docker compose up geen rebuild uit nadat ik het Dockerfile heb gewijzigd?
Omdat up controleert of de image bestaat, niet of deze actueel is.
Wanneer Compose een service start die een build:-sectie bevat, zoekt het naar de image in de lokale image-opslag. Als er al een image met die naam aanwezig is, gebruikt Compose deze. Het leest niet uw Dockerfile, vergelijkt uw bronbestanden niet en kijkt niet naar tijdstempels. De Compose-specificatie definieert de regel als het pull_policy-attribuut, en het standaardgedrag is dat een image alleen wordt gebouwd wanneer deze ontbreekt. Aanwezigheid wordt als voldoende beschouwd.
U bewerkt dus app.py, voert docker compose up -d uit, ziet dat Compose rapporteert dat de container draait, en de oude code wordt geserveerd. Er is niets mislukt, dus er is geen waarschuwing gegeven. Dit is de meest voorkomende melding van "mijn wijziging is niet doorgevoerd" bij Compose. De aanwijzing is de status die Compose naast de containernaam print: een container die door Compose is vervangen, wordt gerapporteerd als recreated of started, en een container die Compose ongemoeid laat, wordt gerapporteerd als running.
Twee controles bieden uitsluitsel. docker compose images toont de image-ID die elke container gebruikt; noteer deze voor de deploy en vergelijk deze daarna. docker image ls heeft een CREATED-kolom, en een image die is aangemaakt vóór uw laatste commit is een verouderde image, ongeacht wat het deploy-script heeft gerapporteerd.
Welke vlaggen dwingen een rebuild af
docker compose up -d --buildbouwt eerst en maakt vervolgens elke container opnieuw aan waarvan de image is gewijzigd. Dit is de vlag waar de meeste mensen naar op zoek zijn.docker compose build webbouwt één service en start niets op. Gebruik daarnadocker compose up --no-deps -d webom alleen die container te vervangen en de rest van de stack draaiende te houden.docker compose build --no-cache webnegeert elke gecachte laag en bouwt opnieuw vanaf de eerste instructie.docker compose build --pullprobeert een nieuwere versie van de base image inFROMop te halen, zodat een variabele tag zoalsnode:22de huidige inhoud ophaalt in plaats van de kopie die u in maart hebt gedownload.docker compose up -d --force-recreatemaakt containers opnieuw aan op basis van de image die ze al gebruiken. Er wordt nooit gebouwd. Dit gebruiken wanneer u eigenlijk--buildbedoelde, is een veelvoorkomend doodlopend spoor.
U kunt de beslissing ook in het bestand vastleggen. Volgens de Compose-specificatie betekent pull_policy: build dat Compose de image bouwt, en deze opnieuw bouwt als deze al aanwezig is. Elke up kost dan tijd voor een build, wat wenselijk is op een laptop, maar zelden wat u wilt op een server.
services:
web:
build: .
image: registry.example.com/acme/web:dev
pull_policy: buildEr is nog één interactie die nuttig is om te kennen. docker compose pull probeert ook images op te halen voor services die een build-sectie hebben; als dat ophalen mislukt, krijgt u de melding dat de image in plaats daarvan gebouwd moet worden. Gebruik --ignore-buildable om die services geruisloos over te slaan.
Hoe de build-cache uw deploy-tijd bepaalt
Elke instructie in een Dockerfile produceert een laag, en de builder hergebruikt een gecachte laag wanneer die instructie en de bijbehorende inputs ongewijzigd zijn. Voor COPY zijn de inputs de inhoud van de bestanden die worden gekopieerd. Zodra één laag de cache mist, wordt elke daaropvolgende laag opnieuw opgebouwd, omdat elke laag wordt gebouwd op het bestandssysteem dat de vorige laag heeft geproduceerd.
Die ene regel bepaalt of uw deploy seconden of minuten duurt. Orden de Dockerfile van wat zelden verandert naar wat bij elke commit verandert.
FROM node:22-slim
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
CMD ["node", "server.js"]npm ci staat boven COPY . ., dus het bewerken van een bronbestand zorgt ervoor dat de installatielaag gecachet blijft en de build hervat bij de kopieerstap. Wissel die twee regels om en een wijziging van één teken zorgt ervoor dat elke afhankelijkheid opnieuw wordt geïnstalleerd, omdat COPY . . de laag ongeldig maakt waarop npm ci is gebouwd. Dezelfde structuur is van toepassing op pip install -r requirements.txt en op go mod download.
--no-cache is het juiste hulpmiddel wanneer u vermoedt dat een verouderde laag uw fix verbergt. Het is een slechte standaardinstelling, omdat het het hergebruik weggooit waarvoor de Dockerfile-volgorde is bedoeld.
Eén ding dat de image instelt en Compose kan overschrijven: de CMD van de Dockerfile is wat de image standaard uitvoert, en een command:-sleutel in de service vervangt dit. Hoe command en entrypoint op elkaar inwerken is hier van belang, omdat een Compose-override ervoor kan zorgen dat een vers gebouwde image zich precies zo gedraagt als de oude.
Build-context en .dockerignore
context: . betekent dat Compose die map inpakt en naar de builder verstuurt voordat de eerste instructie wordt uitgevoerd. Alles daaronder wordt meegestuurd, inclusief .git en elk gegevensbestand dat u toevallig naast uw broncode bewaart. Een build die blijft hangen bij de stap 'transferring context' van een verder ongewijzigd project, geeft aan dat de context te groot is.
Een .dockerignore-bestand in de root van de context sluit paden uit van die overdracht. De syntaxis is vergelijkbaar met .gitignore.
.git
node_modules
*.log
data/
.envDit levert twee voordelen op. De overdracht wordt kleiner, waardoor elke build sneller start. En COPY . . kan niet langer .env naar de image kopiëren, waar iedereen die de image ophaalt het bestand weer kan uitlezen.
Het geval van een trage build die in de loop van de tijd toeneemt, is een bind mount. Een named volume bevindt zich buiten uw projectmap, maar een bind mount zoals ./data:/var/lib/postgresql/data bevindt zich binnen de build-context, waardoor uw builds elke week trager worden naarmate de database groeit. Eén regel in .dockerignore lost dit op. Bind mounts versus named volumes behandelt de bredere afweging.
Build-argumenten dragen een kleinere versie van hetzelfde risico met zich mee. Waarden die worden doorgegeven via args: zijn zichtbaar in de image-geschiedenis voor iedereen die de image in bezit heeft, dus plaats daar een versienummer en nooit een token. Env-bestanden en secrets in Compose behandelt waar inloggegevens in plaats daarvan thuishoren.
Moet u bouwen op de VPS of elders bouwen en pushen?
Bouwen op de server die uw verkeer afhandelt is de standaardmethode omdat dit de kortste route is: git pull, gevolgd door docker compose up -d --build. Dit is prima op een kleine server waar nog niemand afhankelijk van is. Het wordt problematisch om twee redenen die u kunt meten en één reden die pas op een slecht moment aan het licht komt.
Geheugen. Een buildproces voert compilers en bundlers uit naast uw live applicatie, en dit zijn de meest geheugenintensieve onderdelen van de meeste stacks. Op een VPS van 1 GB is een JavaScript-bundler of een Rust-compileerproces routinematig het grootste proces op de machine. Wanneer de kernel geen geheugen meer heeft, beëindigt deze het grootste proces: ofwel de build stopt met Killed en exit-status 137, of uw database wordt beëindigd en de site gaat offline tijdens een deploy. dmesg -T | grep -i oom toont de beëindigingsregel met de procesnaam, zodat u kunt vaststellen wat er is gebeurd in plaats van te gissen.
Schijfruimte. Elke build laat lagen achter en de builder houdt zijn eigen cache bij, los van uw images. docker system df toont beide, en de build-cache groeit alleen maar. Maak ruimte vrij met docker image prune voor zwevende images en docker builder prune voor gecachte lagen. Een volle schijf stopt meer dan alleen de build. De database stopt ook met schrijven, en dat falen kost veel meer dan een trage deploy.
Reproduceerbaarheid. Een image die op de server is gebouwd, bestaat alleen op die server. Een rollback betekent de oude commit uitchecken en opnieuw bouwen, en die build garandeert niet dat u hetzelfde resultaat krijgt, omdat de base-tag is veranderd en de package-mirrors daarmee ook. Elders bouwen en een tag pushen verandert een rollback in een aanpassing: wijs image: naar de vorige tag en voer docker compose up -d uit.
De opstelling die standhoudt is eenvoudig. Uw continuous integration voert de build uit en pusht naar registry.example.com/acme/web:<git-sha>, en het Compose-bestand op de VPS bevat image: zonder enige build:-sleutel. De uitrol bestaat dan uit twee commando's die nauwelijks geheugen vereisen.
docker compose pull
docker compose up -dVoer docker login registry.example.com eenmaal uit op de server en Compose kan vanaf dat moment private tags ophalen.
Behoud de build-sectie voor ontwikkeling in plaats van deze te verwijderen, in een bestand dat u zelf een naam geeft.
# compose.dev.yaml
services:
web:
build:
context: .
pull_policy: builddocker compose -f compose.yaml -f compose.dev.yaml up -d --buildNoem dat bestand compose.dev.yaml en niet compose.override.yaml. Compose laadt automatisch een override-bestand wanneer dit aanwezig is; een verdwaald override-bestand dat naar de server wordt gekopieerd, zou er ongemerkt voor zorgen dat er daar weer gebouwd wordt. Meerdere Compose-bestanden combineren legt uit hoe de merge elke sleutel verwerkt.
De architectuurvalstrik bij het bouwen op een ander systeem
Een image bevat de CPU-architectuur waarvoor deze is gebouwd. Wanneer u bouwt op een Apple Silicon-laptop, de image pusht en vervolgens die tag ophaalt op een x86_64 VPS, waarschuwt Docker dat het gevraagde image-platform niet overeenkomt met het gedetecteerde hostplatform. Het proces stopt vervolgens met exec format error, wat leest als een corrupt binair bestand, terwijl dat niet het geval is. Bouw expliciet voor het doelplatform:
docker buildx build --platform linux/amd64 \
-t registry.example.com/acme/web:1.4.2 --push .Hetzelfde mismatch-probleem treedt in omgekeerde richting op als uw laptop x86 is en u een ARM VPS in plaats van een x86-variant gebruikt. Door CI te laten bouwen op de architectuur waarop u de software uitrolt, wordt dit probleem voorkomen.
Wat te controleren na een deploy
docker compose imagestoont de image en tag van elke actieve container. Een gewijzigde image-ID is het bewijs dat de nieuwe build actief is.docker compose configtoont het samengevoegde bestand na variabele-substitutie, zodat u de uiteindelijke image-naam kunt inzien die Compose gebruikt voordat u acties uitvoert.docker compose logs -f webgedurende de eerste halve minuut na de wissel. Een container die opstart en direct afsluit, blijft in een lus herstarten in plaats van actief te blijven; deze lus is onzichtbaar tenzij u deze controleert.docker image lstoont een CREATED-kolom. Een image die ouder is dan uw laatste commit, is niet opnieuw gebouwd.
Als u nog bezig bent met het samenstellen van het bestand waarop deze controles worden uitgevoerd, behandelt de basis van een Compose-bestand op een VPS de bijbehorende keys, en bevat de spiekbrief voor Compose-commando's de overige subcommando's.
FAQ
Kan ik build en image in dezelfde service gebruiken?
Ja, en dit is de standaardconfiguratie voor een project dat u zelf bouwt. Compose bouwt op basis van de build:-sectie en voorziet het resultaat van de tag die in image: is opgegeven. Die tag is wat docker compose push naar een registry verstuurt en wat een andere machine ophaalt. Zonder image:-sleutel bouwt Compose nog steeds, maar het geeft de image de naam van het project en de service, en geeft een waarschuwing dat het ontbrekende attribuut voorkomt dat de image wordt gepusht.
Waarom pikt docker compose up mijn Dockerfile-wijziging niet op?
Omdat up alleen controleert of er een image met die naam bestaat. Wanneer er een aanwezig is, start Compose deze en vergelijkt deze nooit met uw Dockerfile of uw bronbestanden. Voer docker compose up -d --build uit, of voer docker compose build web uit gevolgd door docker compose up --no-deps -d web om een enkele service te vervangen. Het instellen van pull_policy: build op de service zorgt ervoor dat elke up opnieuw bouwt, wat geschikt is voor een ontwikkelmachine.
Wat is het verschil tussen --build en --force-recreate?
--build bouwt de image opnieuw en recreëert vervolgens de containers waarvan de image is gewijzigd. --force-recreate recreëert containers op basis van de image die ze al hebben, dus het kan nooit een codewijziging oppikken. Als uw wijziging in de broncode of in de Dockerfile zit, is --build de vlag die u nodig heeft. --force-recreate is bedoeld voor het resetten van de container zelf, bijvoorbeeld om de beschrijfbare laag te wissen terwijl de image behouden blijft.
Moet ik mijn Docker-images op de VPS bouwen of ergens anders?
Bouw elders en haal een tag op zodra de server ook verkeer bedient. Een build concurreert met uw applicatie om geheugen, en een kleine VPS lost die concurrentie op door de kernel het grootste proces te laten beëindigen; dit kan de build zijn of uw database. Builds laten ook cache achter op de schijf die door niets automatisch wordt opgeruimd. Bouwen op de server is prima voor een klein project zonder gebruikers, en later verhuizen kost weinig moeite als u de build:-sectie in een Compose-bestand voor alleen ontwikkeling houdt.
Hoe voorkom ik dat de Docker build-cache mijn schijf vult?
Voer docker system df uit om te zien hoeveel ruimte uw images en uw build-cache elk innemen. docker builder prune verwijdert gecachte lagen en docker image prune verwijdert de zwevende images die zijn achtergebleven door eerdere builds. Het toevoegen van -a aan een van beide is agressiever en dwingt uw volgende build om vanaf nul te beginnen. Plan docker system prune -af --volumes niet in op een server, omdat --volumes elk volume verwijdert dat momenteel door geen enkele container wordt gebruikt, en een stack die u voor onderhoud heeft gestopt, houdt uw database in precies zo'n volume.