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:
compose.yamlcompose.ymldocker-compose.yamldocker-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.