Container Lifecycle


stateDiagram-v2
  [*] --> Created
  Created --> Running: docker compose up
  Running --> Healthy: healthcheck passes<br/>stoxx-db, app-airflow-scheduler-1
  Running --> Unhealthy: healthcheck fails<br/>app-airflow-dag-processor-1
  Running --> Exited: process ends or container is stopped
  Exited --> Running: docker start / compose up
  Exited --> Removed: docker rm / compose down

Runtime Inventory

The first lifecycle question is always the same: what exists right now, what is running, and what is only defined on disk? In this project, the answer is different locally and on the Airflow VM, so inspect them separately instead of assuming the two environments mirror each other.

Compose Service Inventory

docker compose ps is the fastest high-signal runtime inventory command in this chapter because it shows lifecycle state, service identity, and published ports in one view. The output below is not theoretical. It comes from the local ESG repo and the live Airflow VM on April 13, 2026.

FieldSource columnUnit / typeMeaning
NAMECompose-generated container namestringThe concrete container identifier used in docker logs, docker exec, and docker inspect.
SERVICECompose service keystringThe logical service defined in the compose file.
STATUSRuntime and health statestringWhether the process is running, exited, healthy, or unhealthy.
PORTSPublished port mapstringWhich ports are reachable from the host, if any.

PowerShell | docker compose ps | inspect the local Windows compose project

After docker compose up, after a reboot, or when a host-side tool cannot reach the local SQL Server or dashboard. It is typically triggered by the repo appears partially up, or you need to verify whether a service exists as a container versus only as a compose definition. PowerShell on the Windows host in C:\Users\aperi\DEV\ESG. This is a read-only inventory command. Confirm which local services are present and whether they are actually serving traffic.

Lists the current local stoxx compose containers and their lifecycle state.

docker compose -f C:\Users\aperi\DEV\ESG\docker-compose.yml ps -a
NAME              IMAGE                                        COMMAND                  SERVICE     CREATED       STATUS                   PORTS
stoxx-dashboard   stoxx-dashboard                              "dotnet ESG.Dashboar…"   dashboard   4 weeks ago   Exited (0) 4 weeks ago
stoxx-db          mcr.microsoft.com/mssql/server:2022-latest   "/bin/bash /entrypoi…"   db          4 weeks ago   Up 9 hours (healthy)     0.0.0.0:1434->1433/tcp, [::]:1434->1433/tcp

This output proves three useful facts immediately. First, the local dashboard exists as a container but is not currently serving traffic because it exited cleanly with code 0. Second, the local SQL Server container is the only active service in the local stack at the moment and its healthcheck is succeeding. Third, the pipeline service is defined in the compose file but has no current container, so any expectation that it is “already up” is wrong until docker compose up pipeline or docker compose up is run.

ColumnValueWatchMeaningImplication
STATUSUp ... (healthy)NormalThe main process is running and the healthcheck is succeeding.Safe as a dependency target for other local services.
STATUSExited (0)ReviewThe main process stopped cleanly.The service is not available until it is recreated or started again.
STATUSExited (nonzero)ActThe container process failed.Inspect logs immediately before recreating the container.
STATUSUp ... (unhealthy)ActThe main process is alive but the healthcheck is failing.The problem may be readiness, not process death.

Linux | docker compose ps | inspect the live Airflow VM compose project

After any VM reboot, Airflow outage, DAG deployment, or healthcheck alarm. It is typically triggered by the Airflow UI, scheduler, worker, or broker appears unavailable, or you need to verify the live container topology before debugging. Linux shell on stoxx-airflow, reached through gcloud compute ssh. This is a read-only inventory command against the live VM. Confirm the real Airflow stack that is currently running in GCP and identify which services expose host ports.

Lists the current live Airflow compose containers on the GCP VM.

gcloud compute ssh stoxx-airflow --project bq-wh-nb --zone europe-west1-b --tunnel-through-iap --command "cd /home/alexper_recovery_gmail_com/app && docker compose ps"
NAME                          IMAGE                 COMMAND                  SERVICE                 CREATED         STATUS                     PORTS
app-airflow-apiserver-1       stoxx-airflow:3.2.0   "/usr/bin/dumb-init …"   airflow-apiserver       9 minutes ago   Up 8 minutes (healthy)     0.0.0.0:8080->8080/tcp, [::]:8080->8080/tcp
app-airflow-dag-processor-1   stoxx-airflow:3.2.0   "/usr/bin/dumb-init …"   airflow-dag-processor   9 minutes ago   Up 8 minutes (unhealthy)   8080/tcp
app-airflow-scheduler-1       stoxx-airflow:3.2.0   "/usr/bin/dumb-init …"   airflow-scheduler       9 minutes ago   Up 8 minutes (healthy)     8080/tcp
app-airflow-triggerer-1       stoxx-airflow:3.2.0   "/usr/bin/dumb-init …"   airflow-triggerer       9 minutes ago   Up 8 minutes (unhealthy)   8080/tcp
app-airflow-worker-1          stoxx-airflow:3.2.0   "/usr/bin/dumb-init …"   airflow-worker          9 minutes ago   Up 7 minutes (healthy)     8080/tcp
app-postgres-1                postgres:16           "docker-entrypoint.s…"   postgres                3 hours ago     Up 3 hours (healthy)       5432/tcp
app-redis-1                   redis:7.2-bookworm    "docker-entrypoint.s…"   redis                   3 hours ago     Up 3 hours (healthy)       6379/tcp

This output is the operational truth for the live VM. The stack is a Compose project named app, not the older repo-documented docker run layout with airflow-webserver, airflow-scheduler, and airflow-triggerer as standalone containers. The VM is running Airflow 3.2.0 with airflow-apiserver, airflow-scheduler, airflow-worker, airflow-dag-processor, airflow-triggerer, PostgreSQL, and Redis. Only the API server publishes host port 8080, which is why UI reachability issues should start with that container and not with the internal services.

FlagSyntaxDescription
-fdocker compose -f <file> ps -aForces Docker to use the intended compose file instead of auto-discovery.
-adocker compose ps -aIncludes stopped containers, which is why the exited local dashboard appears in the output.
--projectgcloud compute ssh ... --project bq-wh-nbTargets the actual GCP project that currently owns stoxx-airflow.
--zonegcloud compute ssh ... --zone europe-west1-bTargets the correct zone for the VM instance.
--tunnel-through-iapgcloud compute ssh ... --tunnel-through-iapUses IAP to reach the VM instead of depending on direct public SSH.
--commandgcloud compute ssh ... --command "<cmd>"Runs a non-interactive remote command without opening a full shell session.

Exec And Logs

Once the inventory is clear, the next step is to prove what the running container can actually do. In this project, docker exec is the fastest way to validate executor mode and database reachability on the Airflow VM, while docker logs is the fastest way to prove that the scheduler is dispatching real work.

Live Runtime Verification

These commands are operationally different from docker compose ps. They do not just report metadata. They cross the container boundary and ask the process itself to identify its version, configuration, database connectivity, and recent background work.

Linux | docker exec | verify the live Airflow scheduler runtime from inside the containers

After Airflow startup, after a Compose refresh, or when task execution does not match the expected executor model. It is typically triggered by the scheduler is running but you need proof that it is on the intended Airflow version, using the intended executor, and still connected to PostgreSQL. Linux shell on stoxx-airflow. The commands below are read-only checks executed inside existing containers. Prove that the live scheduler is really Airflow 3.2.0, that it is using CeleryExecutor, and that the metadata database is reachable from the stack itself.

Reads the Airflow version, executor setting, and PostgreSQL readiness from the live VM containers.

gcloud compute ssh stoxx-airflow --project bq-wh-nb --zone europe-west1-b --tunnel-through-iap --command "docker exec app-airflow-scheduler-1 airflow version && echo '---' && docker exec app-airflow-scheduler-1 airflow config get-value core executor && echo '---' && docker exec app-postgres-1 pg_isready -U airflow"
3.2.0
---
CeleryExecutor
---
/var/run/postgresql:5432 - accepting connections

This is a decisive runtime check. The container is not only present; it is the expected Airflow release, it is running CeleryExecutor rather than LocalExecutor or SequentialExecutor, and PostgreSQL is reachable from inside the stack. That matches the live docker-compose.yaml on the VM, which wires Redis as the broker and PostgreSQL as both metadata database and Celery result backend.

Linux | docker logs | read the scheduler’s recent operational trail

When tasks stay queued, when a DAG appears idle, or when you need to prove that the scheduler is dispatching Cloud Run work. It is typically triggered by airflow UI symptoms do not tell you whether the scheduler is making forward progress. Linux shell on stoxx-airflow. This is a read-only log inspection against the running scheduler container. Show recent scheduling decisions, queue transitions, and successful task completion directly from the scheduler logs.

Dumps the most recent scheduler log lines from the live Airflow VM.

gcloud compute ssh stoxx-airflow --project bq-wh-nb --zone europe-west1-b --tunnel-through-iap --command "docker logs app-airflow-scheduler-1 --tail 30"
2026-04-13T17:30:26.242448Z [info     ] 1 tasks up for execution:
        <TaskInstance: stoxx_stage_yfinance.fetch_bronze_stage_into_gcs manual__2026-04-13T17:28:30Z_serving [scheduled]> [airflow.jobs.scheduler_job_runner.SchedulerJobRunner] loc=scheduler_job_runner.py:665
2026-04-13T17:30:26.249277Z [info     ] Trying to enqueue tasks: [<TaskInstance: stoxx_stage_yfinance.fetch_bronze_stage_into_gcs manual__2026-04-13T17:28:30Z_serving [scheduled]>] for executor: CeleryExecutor(parallelism=32) [airflow.jobs.scheduler_job_runner.SchedulerJobRunner] loc=scheduler_job_runner.py:1030
2026-04-13T17:31:54.635402Z [info     ] Received executor event with state success for task instance TaskInstanceKey(dag_id='stoxx_stage_yfinance', task_id='fetch_bronze_stage_into_gcs', run_id='manual__2026-04-13T17:28:30Z_serving', try_number=1, map_index=-1) [airflow.jobs.scheduler_job_runner.SchedulerJobRunner] loc=scheduler_job_runner.py:1187
2026-04-13T17:31:54.678124Z [info     ] TaskInstance Finished: dag_id=stoxx_stage_yfinance, task_id=fetch_bronze_stage_into_gcs, run_id=manual__2026-04-13T17:28:30Z_serving, map_index=-1, run_start_date=2026-04-13 17:30:27.650234+00:00, run_end_date=2026-04-13 17:31:54.180281+00:00, run_duration=86.530047, state=success, executor=CeleryExecutor(parallelism=32), executor_state=success, try_number=1, max_tries=1, pool=default_pool, queue=default, priority_weight=11, operator=CloudRunExecuteJobOperator, queued_dttm=2026-04-13 17:30:26.244615+00:00, scheduled_dttm=2026-04-13 17:30:26.200354+00:00,queued_by_job_id=13, pid=93 [airflow.jobs.scheduler_job_runner.SchedulerJobRunner] loc=scheduler_job_runner.py:1276
127.0.0.1 - - [13/Apr/2026 17:31:39] "GET /health HTTP/1.1" 200 -

This log proves that the live scheduler is not idle. It is identifying tasks, queueing them for CeleryExecutor, receiving success events back from the executor, and finishing CloudRunExecuteJobOperator tasks for the stoxx_stage_yfinance DAG. The trailing GET /health line also shows that the scheduler health endpoint is answering, which matters because the VM’s scheduler container is currently marked healthy by Docker.

FlagSyntaxDescription
--taildocker logs --tail 30 <container>Limits the log output to the most recent lines so the signal is readable.
--projectgcloud compute ssh ... --project bq-wh-nbForces the command to the live GCP project instead of the outdated repo value.
--zonegcloud compute ssh ... --zone europe-west1-bReaches the correct VM zone.
--tunnel-through-iapgcloud compute ssh ... --tunnel-through-iapUses IAP for VM access.
--commandgcloud compute ssh ... --command "<cmd>"Runs the container inspection non-interactively from the host shell.

Lifecycle Signals

The current project exposes all three lifecycle states that matter in day-to-day Docker operations: healthy long-running services, cleanly exited containers, and running-but-unhealthy containers. Do not flatten those into a single “up” or “down” mental model.

Observed stateReal exampleWhat it means hereNext move
Up ... (healthy)stoxx-db, app-airflow-scheduler-1, app-postgres-1The main process is alive and the probe succeeded.Treat the service as a valid dependency target.
Exited (0)stoxx-dashboardThe process ended cleanly, but the service is not currently serving traffic.Restart or recreate it only if you need that service now.
Up ... (unhealthy)app-airflow-dag-processor-1, app-airflow-triggerer-1The process is alive, but the probe is failing or timing out.Diagnose the healthcheck before restarting the container blindly.

The Airflow VM’s current unhealthy state is not a generic Docker lesson. It is a real compose-health issue in the live stack, and it is analyzed in detail in 03-docker-compose because the root cause sits in the Compose healthcheck definition rather than in the container process model itself.