{"id":"l-dk-3-1","courseId":"docker-kubernetes","lang":"en","kind":"lesson","summary":"Describe notes-api and Postgres in one compose.yaml, start and inspect the stack with <code dir=\"ltr\">docker compose</code>, and connect services by name over Compose’s default network.","html":"<p>notes-api needs Postgres. You could start both with two <code dir=\"ltr\">docker run</code> commands, a hand-made network and a page of flags, and then explain it all again to the next person who joins the team. Compose replaces that with one file in the repository that describes the whole stack, and one command that makes it real.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">The first compose.yaml</h2>\n<p>Create <code dir=\"ltr\">compose.yaml</code> next to the Dockerfile:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"yaml\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">compose.yaml</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">notes</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">services</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  api</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    build</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">.</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    image</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">notes-api:dev</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    ports</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      - </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"8080:3000\"</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    environment</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      PORT</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"3000\"</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      DATABASE_URL</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">postgres://notes:notes@db:5432/notes</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  db</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    image</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">postgres:17</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    environment</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      POSTGRES_USER</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">notes</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      POSTGRES_PASSWORD</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">notes</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      POSTGRES_DB</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">notes</span></span></code></pre></div>\n<p>Two services. <code dir=\"ltr\">api</code> is built from the Dockerfile in this directory and tagged <code dir=\"ltr\">notes-api:dev</code>; <code dir=\"ltr\">db</code> uses the official Postgres 17 image, whose entrypoint creates the user and database from those three variables on first start. <code dir=\"ltr\">name: notes</code> sets the project name, which prefixes everything Compose creates.</p>\n<p>When a service has both <code dir=\"ltr\">build:</code> and <code dir=\"ltr\">image:</code>, Compose builds from the Dockerfile and tags the result with the <code dir=\"ltr\">image:</code> name. That gives the local image a predictable name you can also run by hand, instead of the default name Compose would generate from the project and service names.</p>\n<p>There’s no <code dir=\"ltr\">version:</code> line. Compose v2 implements the Compose Specification, and if you copy an old file that starts with <code dir=\"ltr\">version: &quot;3.8&quot;</code>, Compose ignores it and warns that the attribute is obsolete. Delete it.</p>\n<p>The command is <code dir=\"ltr\">docker compose</code>, with a space. That’s Compose v2, a Docker CLI plugin. The old Python <code dir=\"ltr\">docker-compose</code> with a hyphen has been retired for years, and tutorials that use it are out of date in other ways too.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">Run the stack</h2>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">docker</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> compose</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> up</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -d</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --build</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">docker</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> compose</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> ps</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">docker</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> compose</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> logs</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -f</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> api</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">curl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> localhost:8080/readyz</span></span></code></pre></div>\n<p><code dir=\"ltr\">up</code> builds the image if needed, creates a network, starts both containers and returns. <code dir=\"ltr\">--build</code> forces a rebuild so code changes are picked up. <code dir=\"ltr\">/readyz</code> now answers <code dir=\"ltr\">ready</code>, because the API can reach the database.</p>\n<p><code dir=\"ltr\">docker compose ps</code> shows the containers Compose created, named after the project, the service and a number: <code dir=\"ltr\">notes-api-1</code> and <code dir=\"ltr\">notes-db-1</code>. The number exists because a service can run several identical containers. Every command takes service names, not container names, so you rarely type the full name.</p>\n<p>A few more you’ll use daily:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">docker</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> compose</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> exec</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> db</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> psql</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -U</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -d</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes</span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">   # SQL shell in the db container</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">docker</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> compose</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> restart</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> api</span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">                       # restart one service</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">docker</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> compose</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> down</span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">                              # stop and remove containers and network</span></span></code></pre></div>\n<p><code dir=\"ltr\">up</code> is <strong>declarative</strong>: it compares the file to what’s running and changes only the difference. Edit the API’s environment and run <code dir=\"ltr\">up -d</code> again, and Compose recreates <code dir=\"ltr\">api</code> while <code dir=\"ltr\">db</code> keeps running. Kubernetes works on the same idea at a much larger scale; you’ll meet it in section 4.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">How the services find each other</h2>\n<p>Compose creates a network for the project, <code dir=\"ltr\">notes_default</code>, and attaches every service to it. On that network, Docker’s DNS resolves each <strong>service name</strong> to its container. That’s why <code dir=\"ltr\">DATABASE_URL</code> says <code dir=\"ltr\">@db:5432</code>.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 700 230\" role=\"img\" aria-labelledby=\"l-dk-3-1-t1\"><defs><marker id=\"arrow-l-dk-3-1-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-dk-3-1-t1\">Your laptop reaches the api container through published port 8080 mapped to 3000. Inside the notes_default network, api connects to db on port 5432 by service name. The database has no published port.</title>\n  <rect class=\"d-box\" x=\"20\" y=\"80\" width=\"140\" height=\"70\" rx=\"10\"/>\n  <text class=\"d-label-strong\" x=\"90\" y=\"110\" text-anchor=\"middle\">your laptop</text>\n  <text class=\"d-code\" x=\"90\" y=\"134\" text-anchor=\"middle\">localhost:8080</text>\n  <rect class=\"d-box-primary\" x=\"210\" y=\"20\" width=\"470\" height=\"190\" rx=\"14\"/>\n  <text class=\"d-label-muted\" x=\"445\" y=\"44\" text-anchor=\"middle\">network: notes_default</text>\n  <rect class=\"d-box-accent\" x=\"250\" y=\"80\" width=\"160\" height=\"70\" rx=\"10\"/>\n  <text class=\"d-label-strong\" x=\"330\" y=\"110\" text-anchor=\"middle\">api</text>\n  <text class=\"d-code\" x=\"330\" y=\"134\" text-anchor=\"middle\">:3000</text>\n  <rect class=\"d-box-success\" x=\"490\" y=\"80\" width=\"160\" height=\"70\" rx=\"10\"/>\n  <text class=\"d-label-strong\" x=\"570\" y=\"110\" text-anchor=\"middle\">db</text>\n  <text class=\"d-code\" x=\"570\" y=\"134\" text-anchor=\"middle\">:5432</text>\n  <path class=\"d-arrow\" d=\"M160 115 L248 115\" marker-end=\"url(#arrow-l-dk-3-1-1)\"/>\n  <text class=\"d-label-muted\" x=\"204\" y=\"104\" text-anchor=\"middle\">-p 8080:3000</text>\n  <path class=\"d-arrow\" d=\"M410 115 L488 115\" marker-end=\"url(#arrow-l-dk-3-1-1)\"/>\n  <text class=\"d-code\" x=\"449\" y=\"104\" text-anchor=\"middle\">db:5432</text>\n  <text class=\"d-label-muted\" x=\"570\" y=\"180\" text-anchor=\"middle\">no published port</text>\n</svg><figcaption>Only the API is published; the database is reachable by name inside the project network</figcaption></figure>\n<p>Notice that <code dir=\"ltr\">db</code> has no <code dir=\"ltr\">ports:</code> entry. The API reaches it over the project network; nothing on your laptop or your office network can. If you want to point a desktop SQL client at it, publish it on loopback only: <code dir=\"ltr\">&quot;127.0.0.1:5432:5432&quot;</code>.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">Common mistake</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Using localhost between containers</span></p>\n<p><code dir=\"ltr\">postgres://…@localhost:5432</code> is the most common Compose bug I see. Inside the <code dir=\"ltr\">api</code> container, <code dir=\"ltr\">localhost</code> means the API container itself, and nothing listens on 5432 there. The error is <code dir=\"ltr\">ECONNREFUSED 127.0.0.1:5432</code>. Use the service name.</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">Splitting networks when it matters</h2>\n<p>For bigger stacks you can declare networks and attach each service only where it belongs. A database on a <code dir=\"ltr\">backend</code> network can’t be reached by a web front end that only joins <code dir=\"ltr\">frontend</code>:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"yaml\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">YAML</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">services</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  web</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    networks</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: [</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">frontend</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  api</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    networks</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: [</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">frontend</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">backend</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  db</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    networks</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: [</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">backend</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">networks</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  frontend</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  backend</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span></code></pre></div>\n<p>For notes-api, the default network is enough. Reach for this when a stack has components that should never talk to each other.</p>\n<h2 id=\"sec-5\" tabindex=\"-1\">Where Compose fits</h2>\n<p>Compose is excellent for three jobs: local development, integration tests in CI (start the stack, run the tests, tear it down), and small services that live on a single host. What it doesn’t do is spread containers across machines, move them when a machine dies, or roll out a new version gradually while watching health checks. Those are an orchestrator’s jobs, and they’re what Kubernetes does in section 4. The habits you build here carry straight over: declare the desired state in a file, let a tool make it so, and connect services by name.</p>\n<p>One problem remains. Run <code dir=\"ltr\">docker compose down</code>, then <code dir=\"ltr\">up</code> again, and every note is gone: Postgres stored its data in the container’s writable layer, which <code dir=\"ltr\">down</code> deleted. The next lesson gives the database somewhere permanent to live.</p>\n","toc":[{"id":"sec-1","text":"The first compose.yaml","level":2},{"id":"sec-2","text":"Run the stack","level":2},{"id":"sec-3","text":"How the services find each other","level":2},{"id":"sec-4","text":"Splitting networks when it matters","level":2},{"id":"sec-5","text":"Where Compose fits","level":2}],"takeaways":["A <code dir=\"ltr\">compose.yaml</code> declares services, networks and volumes; <code dir=\"ltr\">docker compose up</code> creates whatever is missing and leaves the rest alone.","Compose v2 is the <code dir=\"ltr\">docker compose</code> plugin; the top-level <code dir=\"ltr\">version:</code> key is obsolete and should be removed.","Every service joins the project’s default network and is reachable by its service name, so the API connects to <code dir=\"ltr\">db:5432</code>, not <code dir=\"ltr\">localhost</code>.","Publish only the ports you need from the host; a database that only the API talks to needs no <code dir=\"ltr\">ports:</code> entry at all."],"further":[{"title":"How Compose works","url":"https://docs.docker.com/compose/intro/compose-application-model/"},{"title":"Networking in Compose","url":"https://docs.docker.com/compose/how-tos/networking/"},{"title":"Compose file reference","url":"https://docs.docker.com/reference/compose-file/"}],"quiz":[{"q":"<p>Inside the <code dir=\"ltr\">api</code> container, which <code dir=\"ltr\">DATABASE_URL</code> host reaches the Postgres service named <code dir=\"ltr\">db</code>?</p>","options":[{"text":"<code dir=\"ltr\">db</code>, the service name, which Compose’s network DNS resolves to the database container.","why":"Correct. Services on the same Compose network find each other by service name."},{"text":"<code dir=\"ltr\">localhost</code>, because both containers run on the same machine.","why":"Each container has its own network namespace. <code dir=\"ltr\">localhost</code> inside <code dir=\"ltr\">api</code> is the API container itself, where nothing listens on 5432."},{"text":"<code dir=\"ltr\">host.docker.internal</code>, which always points at other containers.","why":"That name points at your host machine. It only works if the database port is published there, and it couples the app to your laptop."},{"text":"The database container’s IP address from <code dir=\"ltr\">docker inspect</code>.","why":"It works until the container is recreated and gets a new IP. Names are stable; IPs aren’t."}],"answer":0},{"q":"<p>You add <code dir=\"ltr\">version: &quot;3.8&quot;</code> at the top of <code dir=\"ltr\">compose.yaml</code>. What does current Docker Compose do with it?</p>","options":[{"text":"Switches to the 3.8 file format rules.","why":"Compose v2 implements the single Compose Specification and no longer selects behaviour by version."},{"text":"Fails, because version keys are no longer allowed.","why":"It doesn’t fail. It warns that the attribute is obsolete and ignores it."},{"text":"Ignores it and prints a warning that <code dir=\"ltr\">version</code> is obsolete.","why":"Correct. Delete the line; it does nothing."},{"text":"Downloads the matching Compose release.","why":"Compose never fetches itself based on the file. Your installed <code dir=\"ltr\">docker compose</code> plugin is what runs."}],"answer":2},{"q":"<p>Postgres in your Compose stack is only used by <code dir=\"ltr\">api</code>. Which <code dir=\"ltr\">ports:</code> setting should <code dir=\"ltr\">db</code> have?</p>","options":[{"text":"<code dir=\"ltr\">&quot;5432:5432&quot;</code>, so the API can reach it.","why":"The API reaches it over the Compose network without any published port. Publishing exposes it on every host interface."},{"text":"<code dir=\"ltr\">&quot;0.0.0.0:5432:5432&quot;</code>, to be explicit about listening everywhere.","why":"That’s the same as the default and puts your database on the office Wi-Fi with a development password."},{"text":"<code dir=\"ltr\">&quot;5432&quot;</code>, which publishes it on a random host port.","why":"Still reachable from the host network, just on a less obvious port."},{"text":"None at all; add <code dir=\"ltr\">&quot;127.0.0.1:5432:5432&quot;</code> only if you need a local GUI client.","why":"Correct. Container-to-container traffic doesn’t need published ports, and loopback-only publishing keeps it off the network."}],"answer":3},{"q":"<p>You change only the <code dir=\"ltr\">api</code> service’s environment in <code dir=\"ltr\">compose.yaml</code> and run <code dir=\"ltr\">docker compose up -d</code>. What happens to the running <code dir=\"ltr\">db</code> container?</p>","options":[{"text":"It’s recreated too, because the file changed.","why":"Compose compares each service’s configuration separately. <code dir=\"ltr\">db</code> didn’t change, so it isn’t touched."},{"text":"It keeps running untouched; Compose only recreates services whose configuration changed.","why":"Correct. <code dir=\"ltr\">up</code> reconciles each service against the file and leaves unchanged ones alone."},{"text":"It’s stopped and needs a separate <code dir=\"ltr\">docker compose start db</code>.","why":"<code dir=\"ltr\">up</code> doesn’t stop services that are already in the desired state."},{"text":"It’s paused while <code dir=\"ltr\">api</code> restarts, then resumed.","why":"Compose doesn’t pause dependencies during a recreate."}],"answer":1}],"exercise":{"kind":"spot-bug","language":"yaml","code":"services:\n  api:\n    build: .\n    ports:\n      - \"8080:3000\"\n    environment:\n      DATABASE_URL: postgres://notes:notes@localhost:5432/notes\n  db:\n    image: postgres:17\n    ports:\n      - \"5432:5432\"\n    environment:\n      POSTGRES_USER: notes\n      POSTGRES_PASSWORD: notes\n      POSTGRES_DB: notes","bugLines":[7,11],"title":"Two Compose bugs, one connection","prompt":"<p>With this <code dir=\"ltr\">compose.yaml</code>, the API logs <code dir=\"ltr\">ECONNREFUSED 127.0.0.1:5432</code>, and a colleague on the office Wi-Fi just connected to your database with the password <code dir=\"ltr\">notes</code>. Select the <strong>two</strong> lines responsible.</p>\n","hints":["Inside the <code dir=\"ltr\">api</code> container, what does <code dir=\"ltr\">localhost</code> refer to?","Does the API need a published port to reach the database over the Compose network?"],"explanation":"<p>Line 7 points the API at <code dir=\"ltr\">localhost</code>, which inside its container is the API itself. Use the service name: <code dir=\"ltr\">postgres://notes:notes@db:5432/notes</code>.</p>\n<p>Line 11 publishes Postgres on every host interface, so anyone on your network can reach it. The API doesn’t need it; containers talk over the project network. Remove the <code dir=\"ltr\">ports:</code> block, or bind to loopback if you need a local SQL client:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"yaml\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">YAML</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    ports</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      - </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"127.0.0.1:5432:5432\"</span></span></code></pre></div>\n","checkLabels":{}},"words":708,"readMinutes":3}