Compose

Your compose files, and what picd adds to them.


picd runs docker compose on your own docker engine, so compose works as it does on your machine: builds, volumes, networks, depends_on, health checks, and the rest.

Compose files #

picd deploys the first of these that exists:

  1. compose.yaml
  2. compose.yml
  3. docker-compose.yaml
  4. docker-compose.yml

then the first override that exists, in the same order: compose.override.yaml, compose.override.yml, docker-compose.override.yaml, or docker-compose.override.yml. On top of those it adds every picd file that exists, for settings that only apply on picd: compose.picd.yaml, compose.picd.yml, docker-compose.picd.yaml, docker-compose.picd.yml, picd.yaml, and picd.yml.

COMPOSE_FILE replaces all of this with the files it lists. picd reads it from vars set first, then from the project's committed .env.

Services without a restart or deploy.restart_policy are deployed with unless-stopped, so they come back when your environment restarts. picd keeps any policy you set, including restart: "no".

Routing HTTP #

Route services with Traefik labels, on the picd-ingress network:

 1services:
 2  web:
 3    image: traefik/whoami
 4    labels:
 5      - traefik.enable=true
 6      - traefik.http.routers.web.rule=Host(`web-alice.user.cd.pico.sh`)
 7    networks: [picd-ingress]
 8networks:
 9  picd-ingress:
10    external: true

Hosts must be yours: your subdomains or your custom domains. If a service exposes more than one port, say which with traefik.http.services.<name>.loadbalancer.server.port.

Services that only talk to each other, such as a database, don't need the label or the network; they reach each other by service name on the project's default network. See the database example.

Volumes #

Named volumes and bind mounts under your project, such as ./uploads, persist, as do bind mounts under /data and ~. A bind mount under /tmp gets a directory on your docker engine with 64MB of space, which you can't see from a shell and which is lost when your environment restarts. Bind mounts of other paths fail.

Zero-downtime deploys #

A deploy normally stops a changed service's containers and then starts their replacements, so the service is down in between. To start the new containers first, set order: start-first and give the service a healthcheck:

1services:
2  web:
3    build: .
4    deploy:
5      update_config:
6        order: start-first
7    healthcheck:
8      test: ["CMD", "wget", "-qO-", "http://localhost:8080/health"]
9      interval: 5s

picd starts the new containers beside the old ones and waits until they're healthy. Then it stops the old ones, which get their stop_grace_period to finish their requests. If the new containers aren't healthy within 10 minutes, picd removes them, keeps the old ones running, and the deploy fails.

For a while, two versions of the service run at once, against the same volumes and databases, so changes such as migrations must work with both. A service updates in place, with a warning, if two of its containers can't run together: when it sets container_name or publishes ports. It also updates in place when it has no healthcheck, from its compose file or its image.

Only picd's deploys roll services: a push, or redeploy. Compose itself ignores update_config and warns that it only applies to Swarm, so docker compose up in your shell, or compose up, replaces changed services in place.

Images #

A deploy pulls an image only when your docker engine doesn't have it yet, so a tag that moves, such as latest, keeps the version first pulled. To pull on every deploy, set pull_policy: always:

1services:
2  web:
3    image: ghcr.io/alice/web:latest
4    pull_policy: always

Pinning a version or a digest, such as ghcr.io/alice/web:1.4.2, deploys a new image only when you change the pin.

Builds #

Services with a build section are built on your docker engine on every deploy. Their Dockerfiles' base images are also reused once pulled; set pull: true under build to pull them on every deploy. See the build example.

Ports #

Published ports are published on your environment, not on the internet. To reach a service from outside, route it over HTTP, or publish it on your environment's public IPv6 address.