docker compose config resolves everything, which is the point

A stack assembled from three override files and a set of environment variables is not readable by looking at any of them.

$ docker compose 
    -f docker-compose.yml 
    -f docker-compose.override.yml 
    -f docker-compose.ci.yml config

# prints the merged, interpolated, fully resolved file:
#   every variable substituted
#   every extends flattened
#   every relative path made absolute

$ docker compose config --services
$ docker compose config --quiet && echo valid

Running it with --quiet as a CI step catches an undefined variable before a deploy does, which is the failure that otherwise appears as a container started with an empty environment. It is also the fastest way to answer “which port is actually published” on a project where three files each have an opinion.