CI/CD ·

Как запустить Docker на Mac? Практическая установка Docker Desktop на чипах серии M и развёртывание MySQL, Redis и веб-проекта

Как запустить Docker на Mac? Практическая установка Docker Desktop на чипах серии M и развёртывание MySQL, Redis и веб-проекта

Материал подойдёт разработчикам, которым нужно подготовить локальную среду на Mac с чипом серии M. Мы разберём установку Docker Desktop, проверку архитектуры образов и сборку проекта с MySQL и Redis через Compose, а затем проверим доступность сервисов и сохранение данных.

В Docker Desktop не запускаются контейнеры или приложение не видит базу данных? Быстрый путь: установите Docker Desktop для Apple Silicon, проверьте архитектуру образов, а затем запустите через Docker Compose MySQL, Redis и веб-сервис с правильными адресами и томами.

Подходит: разработчикам, впервые настраивающим контейнерную среду на Mac с чипом серии M, и командам, которым нужно проверить локальную или удалённую среду для разработки. Не подходит: для готового производственного развёртывания. Пример ниже предназначен для локальной разработки и не заменяет настройку защищённой инфраструктуры.

Что нужно проверить до установки Docker на Mac

Docker Desktop на Mac запускает Linux-контейнеры внутри виртуализированной Linux-среды. Это отличается от запуска контейнеров непосредственно в macOS. Для пользователя важны не внутренние механизмы, а два следствия: Docker Desktop должен поддерживаться вашей версией macOS, а образ контейнера должен подходить архитектуре среды исполнения. Условия установки и доступные варианты загрузки сверяйте с документацией Docker Desktop для Mac. Связь виртуализации с запуском Linux на Mac описана в документации Apple о Linux в виртуальной машине.

Как установить Docker Desktop на Mac с чипом серии M и понять, что Docker работает? Скачайте установщик для Apple Silicon с официальной страницы, завершите установку и откройте Docker Desktop. После запуска проверьте в приложении, что движок работает, затем выполните docker version и docker compose version. Первая команда должна показать сведения и о клиенте, и о сервере Docker. Если выводится только информация о клиенте или появляется ошибка подключения, проверьте состояние Docker Desktop, а не переходите сразу к настройке проекта.

До запуска проекта зафиксируйте три условия:

  • Docker Desktop установлен и завершил запуск.
  • Образ каждого сервиса подходит для используемой архитектуры.
  • В Compose-файле заданы адреса сервисов, порты и постоянные тома.

На этом этапе не нужно угадывать требования к ресурсам или выбирать особые параметры запуска. Сначала проверьте поддерживаемую конфигурацию по официальной документации, а затем наблюдайте за фактическим состоянием контейнеров.

Первый запуск: проверяем клиент, сервер и образ

После установки выполните команды в терминале:

bash
docker version
docker compose version
docker run --rm hello-world

Последняя команда загружает и запускает небольшой тестовый контейнер, а затем удаляет его. Успешное сообщение показывает, что клиент смог обратиться к движку, а тот — запустить контейнер. Если ошибка указывает на недоступный сервер, откройте Docker Desktop и дождитесь готовности движка. Если не скачивается образ, проверьте сетевое подключение и повторите команду после устранения причины.

Для проверки архитектуры образа используйте:

bash
docker buildx imagetools inspect mysql:8.4
docker buildx imagetools inspect redis:7

Список платформ в выводе помогает выяснить, какие варианты опубликованы для образа. Общий принцип выбора платформ и сборки образов описан в документации Docker по многоплатформенным образам. Для конкретных образов сверяйтесь с описанием официального образа MySQL и описанием официального образа Redis.

Что делать, если Docker сообщает о несовместимой архитектуре? Сначала найдите сведения о платформах в описании именно этого образа и изучите сообщение запуска. Не добавляйте параметр --platform наугад: принудительный выбор платформы не подтверждает совместимость приложения и может привести к запуску через эмуляцию. Если образ сторонний, уточните у его сопровождающих, публикуется ли сборка для нужной платформы.

Если контейнер завершается, команда docker ps его не покажет. Используйте docker ps -a и docker logs ИМЯ_ИЛИ_ID, чтобы найти остановившийся контейнер и прочитать причину завершения.

Шаг 1. Создаём проект и Compose-конфигурацию

Создайте каталог проекта и внутри него файлы compose.yaml, Dockerfile, package.json и app.js. В Compose каждый сервис получает имя, по которому другие контейнеры могут обращаться к нему в общей сети. Проброс портов нужен для подключения с Mac; тома отделяют сохраняемые данные от жизненного цикла контейнера. Эти роли параметров и сетей описаны в справочнике Compose по настройке сервисов.

Сохраните в compose.yaml такую конфигурацию:

yaml
services:
  mysql:
    image: mysql:8.4
    environment:
      MYSQL_DATABASE: appdb
      MYSQL_USER: app
      MYSQL_PASSWORD: ${MYSQL_PASSWORD:?Set MYSQL_PASSWORD in .env}
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:?Set MYSQL_ROOT_PASSWORD in .env}
    ports:
      - "127.0.0.1:3306:3306"
    volumes:
      - mysql_data:/var/lib/mysql
    healthcheck:
      test:
        - CMD-SHELL
        - mysqladmin ping -h 127.0.0.1 -uroot -p"$${MYSQL_ROOT_PASSWORD}" --silent
      interval: 5s
      timeout: 5s
      retries: 20

  redis:
    image: redis:7
    command: ["redis-server", "--appendonly", "yes"]
    ports:
      - "127.0.0.1:6379:6379"
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 20

  web:
    build: .
    environment:
      PORT: "3000"
      MYSQL_HOST: mysql
      MYSQL_DATABASE: appdb
      MYSQL_USER: app
      MYSQL_PASSWORD: ${MYSQL_PASSWORD:?Set MYSQL_PASSWORD in .env}
      REDIS_URL: redis://redis:6379
    ports:
      - "127.0.0.1:3000:3000"
    depends_on:
      mysql:
        condition: service_healthy
      redis:
        condition: service_healthy
    healthcheck:
      test:
        - CMD
        - node
        - -e
        - "fetch('http://127.0.0.1:3000/health').then(r => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))"
      interval: 10s
      timeout: 5s
      retries: 10

volumes:
  mysql_data:
  redis_data:

В этой конфигурации mysql и redis — имена внутренних сервисов, а не адреса localhost. Поэтому веб-приложение подключается к MySQL по mysql:3306, к Redis по redis:6379. Адрес 127.0.0.1 в публикации портов, напротив, ограничивает доступ к сервисам с самого Mac. Порты 3306, 6379 и 3000 здесь заданы как выбранные значения для проекта: при совпадении с уже занятым портом измените левую часть записи, например 127.0.0.1:3307:3306. Механизм сети Docker Desktop для доступа между хостом и контейнерами объяснён в документации по сетевым возможностям Docker Desktop.

Почему одного depends_on недостаточно? Простое указание порядка запуска не означает, что база уже принимает подключения. Здесь веб-сервис ожидает состояния service_healthy, а MySQL и Redis проверяют свою готовность. Такой подход опирается на описание порядка запуска и проверок готовности в Compose. Он снижает вероятность раннего подключения приложения к ещё не готовой базе, но не исправляет неверный пароль или ошибку в коде.

Шаг 2. Добавляем веб-сервис с проверкой MySQL и Redis

В Dockerfile укажите базовый образ Node.js и установку зависимостей проекта:

dockerfile
FROM node:22-alpine
WORKDIR /app
COPY package.json ./
RUN npm install --omit=dev
COPY app.js ./
EXPOSE 3000
CMD ["node", "app.js"]

В package.json задайте зависимости приложения:

json
{
  "name": "mac-compose-demo",
  "private": true,
  "version": "1.0.0",
  "scripts": {
    "start": "node app.js"
  },
  "dependencies": {
    "mysql2": "^3.11.0",
    "redis": "^4.7.0"
  }
}

Значение version здесь описывает пакет приложения, а не гарантирует версию его зависимостей. Для повторяемых сборок в команде сохраните package-lock.json и используйте npm ci; не полагайтесь на диапазоны зависимостей при строгом требовании воспроизводимости.

В app.js поместите минимальную проверку соединений:

javascript
const http = require("node:http");
const mysql = require("mysql2/promise");
const { createClient } = require("redis");

const pool = mysql.createPool({
  host: process.env.MYSQL_HOST,
  user: process.env.MYSQL_USER,
  password: process.env.MYSQL_PASSWORD,
  database: process.env.MYSQL_DATABASE,
  waitForConnections: true
});

const redis = createClient({ url: process.env.REDIS_URL });
redis.on("error", (error) => console.error("Redis:", error.message));

async function connectWithRetry() {
  for (;;) {
    try {
      await pool.query("SELECT 1");
      await redis.connect();
      return;
    } catch (error) {
      console.error("Ожидание баз данных:", error.message);
      await new Promise((resolve) => setTimeout(resolve, 2000));
    }
  }
}

const server = http.createServer(async (_request, response) => {
  try {
    const [rows] = await pool.query("SELECT 1 AS mysql_ok");
    const count = await redis.incr("web:visits");
    response.writeHead(200, { "content-type": "application/json" });
    response.end(JSON.stringify({
      mysql: rows[0].mysql_ok,
      redis_counter: count
    }));
  } catch (error) {
    console.error(error);
    response.writeHead(503, { "content-type": "application/json" });
    response.end(JSON.stringify({ error: "service unavailable" }));
  }
});

connectWithRetry().then(() => {
  server.listen(Number(process.env.PORT || 3000), "0.0.0.0");
});

Приложение использует имена сервисов Compose, а не localhost: внутри контейнера localhost указывает на сам контейнер. Обработчик /health из этого минимального примера не выделен отдельно, поэтому добавьте его, чтобы проверка готовности из Compose получила корректный ответ. Замените обработчик сервера таким вариантом:

javascript
const server = http.createServer(async (request, response) => {
  try {
    const [rows] = await pool.query("SELECT 1 AS mysql_ok");
    await redis.ping();

    response.writeHead(200, { "content-type": "application/json" });
    response.end(JSON.stringify({
      mysql: rows[0].mysql_ok,
      redis: "PONG"
    }));
  } catch (error) {
    console.error(error);
    response.writeHead(503, { "content-type": "application/json" });
    response.end(JSON.stringify({ error: "service unavailable" }));
  }
});

Так веб-проверка сообщает о состоянии подключений к обоим хранилищам. Если нужно проверять именно запись в Redis, используйте INCR в отдельном маршруте приложения и проверьте, что значение меняется при повторном запросе. Подключение к MySQL и назначение переменных окружения также сверяйте с инструкцией MySQL по запуску в Docker.

Шаг 3. Задаём локальные пароли и запускаем сервисы

Создайте рядом с compose.yaml файл .env:

dotenv
MYSQL_PASSWORD=local-app-change-this
MYSQL_ROOT_PASSWORD=local-root-change-this

Это фиктивные значения для локальной проверки. Не переносите их в опубликованный репозиторий и не используйте в производственной среде. Добавьте .env в .gitignore. Для реального окружения применяйте отдельный механизм управления секретами и отдельные учётные данные.

Проверьте собранную конфигурацию и запустите проект:

bash
docker compose config
docker compose up --build -d
docker compose ps

Если проверка конфигурации сообщает, что переменная не задана, проверьте расположение .env и точное написание имён. Если контейнер не стал healthy, запросите журнал соответствующего сервиса:

bash
docker compose logs --tail=100 mysql
docker compose logs --tail=100 redis
docker compose logs --tail=100 web

Не считайте завершение docker compose up доказательством готовности приложения: сопоставьте состояние сервисов в docker compose ps, их журналы и реальный запрос к веб-приложению.

Шаг 4. Проверяем доступ с Mac и сохранение данных

Откройте http://localhost:3000/health в браузере или выполните:

bash
curl -i http://localhost:3000/health

Успешный ответ должен содержать результат запроса к MySQL и ответ Redis. Это проверяет не только публикацию веб-порта, но и внутренние соединения контейнеров. Если страница недоступна, сначала проверьте состояние web и публикацию порта в выводе docker compose ps. Затем смотрите журнал приложения и только после этого проверяйте учётные данные и имена хостов.

Отдельно проверьте Redis из контейнера:

bash
docker compose exec redis redis-cli SET check-key saved
docker compose exec redis redis-cli GET check-key

Для MySQL подключитесь клиентом внутри контейнера:

bash
docker compose exec mysql mysql -uapp -p appdb

Введите значение MYSQL_PASSWORD, а затем проверьте запросом:

sql
SELECT 1;

Том mysql_data хранит файлы MySQL за пределами изменяемого слоя контейнера. redis_data используется Redis с включённым журналированием AOF. Пересоздание контейнера командой docker compose down удаляет контейнеры, но именованные тома по умолчанию сохраняются. Удаление с docker compose down -v удаляет и тома — не выполняйте эту команду, если данные нужно оставить. Для проверки сначала запишите тестовое значение, перезапустите проект и прочитайте его снова. Сохранение данных относится к конфигурации именно этого Compose-проекта и не является резервным копированием.

Типовые сбои и способ отличить причину

Образ не подходит по архитектуре. Проверьте платформы в выводе docker buildx imagetools inspect и сведения сопровождающих образа. Сопоставьте их с текстом ошибки и журналом контейнера. Не меняйте архитектуру принудительно, пока не подтвердите поддержку образа.

Порт уже занят. Смотрите сообщение Docker при запуске и опубликованные порты в docker compose ps. Поменяйте только порт на стороне Mac, например левое значение в 127.0.0.1:3307:3306; внутренний порт сервиса оставьте соответствующим его конфигурации. Если порт изменился у веб-сервиса, открывайте новый адрес в браузере.

Не совпадает пароль или имя базы. Сверьте .env и переменные сервиса web с настройками mysql. Проверьте журналы MySQL и веб-приложения. Если том базы уже создан, изменение переменной и повторный запуск не обязательно переинициализирует существующую базу: изменение начальных параметров не стирает уже записанные данные автоматически.

Сервис ещё не готов. Сопоставьте результат проверки healthcheck с логами. Если MySQL или Redis не стали healthy, сначала устраните причину в соответствующем сервисе. Увеличение ожидания без анализа журналов лишь откладывает обнаружение ошибки.

Данные исчезли после пересоздания. Убедитесь, что сервис подключён к именованному тому и запуск не сопровождался down -v. Проверьте список томов командой docker volume ls. Удалённый том не восстанавливается командой Compose: для важных данных заранее настройте отдельное резервное копирование.

Для локальной разработки опубликованные здесь порты привязаны к loopback-интерфейсу. Не убирайте это ограничение и не используйте демонстрационные пароли, если сервисы становятся доступны из сети.

Как выбрать между локальной и удалённой средой

ВариантОценка для этой задачиКогда разумно выбирать
Docker Desktop на собственном MacВысокая для ежедневной локальной разработки; состояние и данные остаются под вашим контролемПроект нужен рядом с редактором кода, а Mac можно оставлять доступным
Удалённый MacВысокая, если рабочее место требуется отделить от личного компьютера или организовать удалённый доступНужно подключаться к среде извне или не держать рабочий процесс на локальном устройстве
Производственный серверНизкая для повторения локальной разработки без адаптацииСервис должен обслуживать пользователей; тогда необходимы отдельные меры безопасности, резервирования и эксплуатации

Перед переносом проекта на удалённую машину отдельно оцените доступ к портам, хранение секретов, резервное копирование томов и способ удалённого подключения. Если такой вариант подходит, сравните условия на странице удалённого Mac от ZekVPS; сведения о конкретном предложении для региона проверяйте на странице ZekVPS о Mac в Гонконге. Мы не предполагаем здесь никаких характеристик или результатов для удалённых конфигураций.

Контрольный список перед работой над проектом

  • [ ] Docker Desktop запущен, а docker version показывает клиент и сервер.
  • [ ] Для образов MySQL и Redis проверены опубликованные платформы.
  • [ ] docker compose config завершается без ошибок переменных.
  • [ ] Веб-контейнер использует имена сервисов mysql и redis, а не localhost.
  • [ ] Веб-страница отвечает, запрос к MySQL проходит, Redis принимает запись и чтение.
  • [ ] Томам назначено сохранение данных; команда удаления томов не используется случайно.
  • [ ] Тестовые пароли не применяются вне локальной разработки.

Если локальный Mac справляется с проектом и его можно оставить включённым, Docker Desktop — прямой путь к среде разработки без отдельного удалённого рабочего места. Если локальный компьютер неудобно постоянно держать доступным или требуется подключаться к Mac удалённо, изучите подходящую среду ZekVPS; если же задача связана с постоянной производственной нагрузкой, сначала проектируйте производственное развёртывание, а не переносите демонстрационный Compose без изменений.

Перенесите разработку на удалённый Mac

Арендуйте в ZekVPS выделенный Mac mini M4 с полноценной macOS для разработки и тестирования проектов.

Подключайтесь к удалённой машине по SSH или VNC и работайте в привычном окружении.

Чтобы перевести MCP или Agent из демо в ежедневную работу, сначала зафиксируйте облачный Mac со снимками. Смотреть тарифы ZekVPS Mac mini — Разделите лабораторию и рабочий стол — деплой станет спокойнее.

Акция