Skip to content

Nekspert/Compose-CMD-Playbook

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 

Repository files navigation

Compose-CMD-Playbook: CMD vs CMD-SHELL and $VAR vs $$VAR


Примеры (start with two different docker-compose.yml snippets)

Example 1 (postgres, redis)

services:
  postgres:
    image: postgres:17-alpine
    container_name: postgres
    env_file:
      - .env
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - ./.postgres_data:/var/lib/postgresql/data
    ports:
      - "${POSTGRES_PORT}:5432"
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
      interval: 5s
      timeout: 5s
      retries: 5
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "5"
        compress: "true"
    networks:
      - app_network

  redis:
    image: redis:7
    container_name: redis
    env_file:
      - .env
    volumes:
      - ./.redis_data:/data
    restart: unless-stopped
    ports:
      - "${REDIS_PORT}:6379" # <-- правильно: ${REDIS_PORT}
    command: ["redis-server", "--appendonly", "yes", "--requirepass", "${REDIS_PASSWORD}"]
    healthcheck:
      test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "PING"]
      interval: 10s
      timeout: 3s
      retries: 3
      start_period: 5s
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "5"
        compress: "true"

networks:
  app_network:
    name: app_network

Example 2 (db + redis)

services:
  db:
    image: postgres:10
    container_name: postgres
    restart: unless-stopped
    env_file: .env
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER}"]
      interval: 10s
      timeout: 5s
      retries: 3
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
    ports:
      - "5433:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data

  redis:
    image: redis:7
    container_name: redis
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 3
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
    ports:
      - "6380:6379"
    volumes:
      - redis_data:/data

volumes:
  pgdata:
  redis_data:

Кратко: CMD vs CMD-SHELL

  • CMD (exec form)

    • Пример: test: ["CMD", "redis-cli", "ping"]
    • Docker запускает программу напрямую (без shell).
    • Преимущества: быстрее, безопаснее.
    • Ограничения: не работают $VAR, пайпы, &&/||.
  • CMD-SHELL (shell form)

    • Пример: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
    • Docker выполняет /bin/sh -c "...".
    • Преимущества: работают переменные окружения, логические операторы, пайпы.
    • Когда использовать: если команда использует переменные или сложную логику.

Правило:

  • Простая команда → CMD.
  • Нужны переменные / логика → CMD-SHELL.

Коротко: ${VAR} vs $$VAR

  • ${VAR} (один знак $)

    • Подставляется docker-compose ДО запуска контейнера.
    • Используется в environment, ports, volumes, и т.д.
    • Пример: POSTGRES_DB: ${POSTGRES_DB} — docker-compose заменит это значением из .env.
  • $$VAR (двойной знак $$)

    • Нужен, чтобы передать одиночный $ в контейнер.
    • docker-compose превращает $$VAR в $VAR, и уже в контейнере shell подставляет значение.
    • Используется в CMD-SHELL когда нужно, чтобы sh внутри контейнера сделал подстановку.
    • Пример: test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER}"]

Мнемоника:

  • Один $ — docker-compose.
  • Два $$ — до контейнера должен дойти $.

Почему оба варианта работают, но отличаются

  • ${VAR}: значение подставляется на хосте, докер-контейнер получает уже готовую команду.
  • $$VAR: докер передаёт литерал $VAR в контейнер, а уже sh внутри контейнера разворачивает переменную.

Пример:

  • test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"] — host-side expansion
  • test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER}"] — container-side expansion

Common pitfalls / Частые ошибки

  • Ошибка в синтаксисе порта: {"$REDIS_PORT":6379} или {$REDIS_PORT}:6379всегда используйте "${REDIS_PORT}:6379".
  • Путать CMD и CMD-SHELL: если команда использует $VAR, CMD не сработает.
  • Писать CMD-SHELL без экранирования $ если хотите, чтобы переменная подставлялась внутри контейнера.

Рекомендации (best practices)

  • Для healthcheckов, которые зависят от env vars, **используйте CMD-SHELLс$$`**, чтобы исключить окружные эффекты:
healthcheck:
  test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
  • Для простых проверок без переменных используйте CMD (меньше слоёв, быстрее).
  • Всегда храните чувствительные значения в .env (и добавьте .env в .gitignore).
  • Проверяйте синтаксис YAML — лишние/неправильные кавычки или фигурные скобки ломают парсер.

Quick checklist

  • Нужно ли в команде $VAR? → да → CMD-SHELL (и подумать о $$)
  • Команда простая (нет переменных)? → CMD
  • Правильно указываит порты — "${PORT}:<container_port>"

TL;DR

  • CMD — exec, без shell, нет подстановки переменных.
  • CMD-SHELL — через shell, поддерживает $VAR, логические операторы.
  • ${VAR} — подставляет docker-compose до запуска контейнера.
  • $$VAR — нужен, если хотите, чтобы переменная была подставлена внутри контейнера.

About

CMD and "$", "$$" behavior description in README.md file

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors