Docker volumes: как хранить данные контейнеров и не потерять их после prune

docker volumes хранение данных
Быстрый ответ
Docker volumes — это три разных механизма с разными гарантиями сохранности:
  • Named volume — данные под управлением Docker (/var/lib/docker/volumes/), переживают пересоздание контейнера, теряются при docker system prune --volumes
  • Bind mount — данные в произвольной папке хоста, ты контролируешь путь и бэкап, идеален для баз данных в продакшне
  • tmpfs — данные в оперативной памяти, исчезают при остановке контейнера, только для временных данных

Правило продакшна: базы данных и критичные данные — только bind mount или named volume с настроенным бэкапом. Никогда не запускай docker system prune --volumes без бэкапа.

Диагноз: где пропадают данные

Типичная история. Поднял стек: PostgreSQL, Redis, приложение. Работало месяц. Потом решил почистить диск:


docker compose down
docker system prune --volumes

После этого docker compose up -d поднял контейнеры. Всё запустилось. Только базы данных пустые. Пользователи, заказы, настройки — gone.

Хранение данных Docker volumes — это тема где цена ошибки равна потере всего. Эта статья закрывает вопрос полностью: разберём все три типа хранилищ, настроим правильно для продакшна, сделаем бэкап который реально восстанавливается, и перенесём данные на другой сервер.

Что нужно: Docker 24+, docker compose v2, доступ к серверу по SSH. Времени — час на первичную настройку.

Что будет в статье:

  • Чем отличаются named volume, bind mount и tmpfs — с примерами
  • Как правильно настроить docker-compose.yml для продакшна
  • Бэкап volumes: tar, rsync, cron
  • Восстановление из бэкапа — полный сценарий
  • Миграция данных между серверами
  • Что делать если уже потерял данные после prune

Как Docker хранит данные: три механизма

Контейнер — это процесс с изолированной файловой системой. По умолчанию все файлы внутри контейнера живут в его writable layer. Удалил контейнер — удалил данные. Вот три способа это исправить:

Тип Где хранится Кто управляет Использование
Named volume /var/lib/docker/volumes/ Docker daemon Продакшн, БД
Bind mount Любой путь на хосте Ты Конфиги, данные БД где нужен прямой доступ
tmpfs mount Оперативная память Ядро Секреты, кэш, временные данные

Важное уточнение: bind mount, где ты указываешь путь вида ./data:/var/lib/postgresql/data — это не то же самое что named volume. Bind mount привязан к конкретной папке на хосте. Named volume — абстракция которую Docker прячет в /var/lib/docker/volumes/. Оба переживают пересоздание контейнера, но ведут себя по-разному при prune.

%%{init: {
  'theme': 'base',
  'themeVariables': {
    'primaryColor': '#ffffff',
    'primaryTextColor': '#1e293b',
    'primaryBorderColor': '#94a3b8',
    'lineColor': '#64748b',
    'fontSize': '15px',
    'fontFamily': 'ui-sans-serif, system-ui, sans-serif'
  },
  'flowchart': {'curve': 'linear', 'nodeSpacing': 50, 'rankSpacing': 50}
}}%%
flowchart TD
    C["Контейнер"] --> A["Named Volume"]
    C --> B["Bind Mount"]
    C --> D["tmpfs"]
    A --> E["Docker Area /var/lib/docker/volumes/"]
    B --> F["Любой путь хоста /home/user/data/"]
    D --> G["RAM только пока контейнер работает"]
    style C fill:#f8fafc,stroke:#3b82f6,stroke-width:2px,color:#1e40af
    style A fill:#f8fafc,stroke:#22c55e,stroke-width:2px,color:#15803d
    style B fill:#f8fafc,stroke:#22c55e,stroke-width:2px,color:#15803d
    style D fill:#f8fafc,stroke:#f97316,stroke-width:2px,color:#9a3412
    style E fill:#f8fafc,stroke:#94a3b8,stroke-width:1px,color:#1e293b
    style F fill:#f8fafc,stroke:#94a3b8,stroke-width:1px,color:#1e293b
    style G fill:#f8fafc,stroke:#ef4444,stroke-width:1px,color:#7f1d1d

Named volume: синтаксис и поведение

Named volume создаётся явно и живёт отдельно от контейнера. Синтаксис в docker-compose.yml:


services:
  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: myapp
    volumes:
      - pgdata:/var/lib/postgresql/data
    restart: unless-stopped

volumes:
  pgdata:
    driver: local

Когда делаешь docker compose down — контейнер удаляется, volume pgdata остаётся. Когда делаешь docker compose up — контейнер пересоздаётся и подключается к тому же volume. Данные на месте.

Посмотри где физически лежит volume:


docker volume inspect pgdata

Вывод покажет Mountpoint — обычно что-то вроде /var/lib/docker/volumes/myproject_pgdata/_data. Это реальная папка на хосте. Можно зайти и посмотреть файлы напрямую от root.

Теперь про убийцу: docker system prune --volumes удаляет все volumes которые не подключены ни к одному запущенному контейнеру. Сделал docker compose down — контейнеры остановились и удалились, volume стал «сиротой». Следующий prune с флагом —volumes снесёт его без вопросов. Это не баг, это задокументированное поведение.

Критически важно
docker system prune без флага —volumes не трогает тома. Опасность появляется только с явным —volumes. Но многие копируют команду из интернета не читая что делает каждый флаг. Это классика жанра.

Bind mount: ты контролируешь путь

Bind mount — прямой проброс папки с хоста в контейнер. Указываешь абсолютный или относительный путь:


services:
  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: myapp
    volumes:
      - /opt/appdata/postgres:/var/lib/postgresql/data
    restart: unless-stopped

Данные лежат в /opt/appdata/postgres на хосте. Ты знаешь точно где. Бэкап делается обычным tar или rsync. docker system prune --volumes их не затронет никогда — это файловая система хоста, не Docker volume.

Минус: права доступа. PostgreSQL внутри контейнера работает от пользователя с UID 999. Если создал папку от root — Postgres не запустится и скажет что не может записать данные. Правим так:


mkdir -p /opt/appdata/postgres
chown -R 999:999 /opt/appdata/postgres

Для MySQL/MariaDB UID будет 999, для Redis — 999, для Nginx — 101. Смотри документацию образа или запусти контейнер без bind mount и проверь от кого работает процесс:


docker run --rm postgres:16-alpine id postgres

Правильная структура каталогов для продакшна

За годы работы выработалась структура которую можно использовать как шаблон. Всё организовано так что бэкап одной папки покрывает всё:


/opt/
└── apps/
    └── myproject/
        ├── docker-compose.yml
        ├── .env
        └── data/
            ├── postgres/
            ├── redis/
            ├── uploads/
            └── backups/

docker-compose.yml с bind mount под эту структуру:


services:
  postgres:
    image: postgres:16-alpine
    env_file: .env
    volumes:
      - ./data/postgres:/var/lib/postgresql/data
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    volumes:
      - ./data/redis:/data
    command: redis-server --appendonly yes
    restart: unless-stopped

  app:
    image: myapp:latest
    volumes:
      - ./data/uploads:/app/uploads
    depends_on:
      - postgres
      - redis
    restart: unless-stopped

Плюс такого подхода: rsync -avz /opt/apps/myproject/data/ backup-server:/backups/myproject/ — и у тебя полный бэкап всего проекта. Одна команда, один путь, ничего не забудешь.

Когда брать named volume, а когда bind mount

Сценарий Рекомендация Почему
БД в продакшне Bind mount Прямой контроль над путём, простой бэкап
Shared volume между контейнерами Named volume Docker управляет правами и путём
Конфиги и сертификаты Bind mount (readonly) Версионирование в git, чёткий путь
Данные которые не нужны после контейнера tmpfs Не засоряет диск, быстрее
Dev-окружение Bind mount от home Прямой доступ к файлам для правки

Бэкап named volume: три подхода

Ситуация: у тебя уже есть named volume и менять конфиг нет возможности. Или ты принципиально используешь named volumes. Как делать бэкап.

Подход 1: временный контейнер с tar

Стандартный способ из документации Docker. Останови контейнер, запусти временный, смонтируй volume, упакуй tar:

Бэкап named volume через временный контейнер
Останови контейнер с данными. Запусти временный alpine с двумя маунтами: volume и папка для бэкапа. Упакуй tar и выйди. Данные сохранены на хосте.

# Останови контейнер (важно для консистентности данных)
docker compose stop postgres

# Создай бэкап
docker run --rm \
  -v myproject_pgdata:/source:ro \
  -v /opt/backups:/backup \
  alpine \
  tar czf /backup/pgdata-$(date +%Y%m%d-%H%M%S).tar.gz -C /source .

# Запусти контейнер обратно
docker compose start postgres

Флаг :ro у source монтирует volume в read-only. Это защита от случайной записи во время бэкапа. Не пренебрегай.

Подход 2: pg_dump для PostgreSQL

Для баз данных горячий бэкап через pg_dump лучше холодного копирования файлов. Не нужно останавливать контейнер. Дамп консистентен даже при активных транзакциях:


# Логический дамп без остановки контейнера
docker exec postgres_container pg_dumpall -U postgres | \
  gzip > /opt/backups/postgres-$(date +%Y%m%d-%H%M%S).sql.gz

Для конкретной базы:


docker exec postgres_container \
  pg_dump -U postgres myapp | \
  gzip > /opt/backups/myapp-$(date +%Y%m%d-%H%M%S).sql.gz

Подход 3: bind mount + rsync

Если перешёл на bind mount — бэкап максимально простой:


# Локальный бэкап с ротацией
rsync -avz --delete \
  /opt/apps/myproject/data/ \
  /opt/backups/myproject/

# Или сразу на удалённый сервер
rsync -avz -e "ssh -p 22" \
  /opt/apps/myproject/data/ \
  user@backup-server:/backups/myproject/

Автоматический бэкап через cron

Ручной бэкап который не запускается автоматически — это не бэкап, это намерение. Делаем скрипт и вешаем на cron.

Создай скрипт /opt/scripts/backup-docker.sh:


#!/bin/bash

set -euo pipefail

BACKUP_DIR="/opt/backups/docker"
DATE=$(date +%Y%m%d-%H%M%S)
KEEP_DAYS=7
LOG_FILE="/var/log/docker-backup.log"

mkdir -p "$BACKUP_DIR"

log() {
  echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

log "=== Начало бэкапа ==="

# PostgreSQL - логический дамп
log "Бэкап PostgreSQL..."
if docker exec myproject_postgres_1 pg_dumpall -U postgres | \
   gzip > "${BACKUP_DIR}/postgres-${DATE}.sql.gz"; then
  log "PostgreSQL OK: ${BACKUP_DIR}/postgres-${DATE}.sql.gz"
else
  log "ОШИБКА: PostgreSQL бэкап не удался"
  exit 1
fi

# Файлы загрузок через rsync
log "Бэкап uploads..."
rsync -avz --delete \
  /opt/apps/myproject/data/uploads/ \
  "${BACKUP_DIR}/uploads/"
log "Uploads OK"

# Удаляем старые бэкапы
find "$BACKUP_DIR" -name "postgres-*.sql.gz" -mtime +"$KEEP_DAYS" -delete
log "Старые бэкапы удалены (старше ${KEEP_DAYS} дней)"

log "=== Бэкап завершён ==="

chmod +x /opt/scripts/backup-docker.sh

Добавь в crontab:


crontab -e

# Бэкап каждый день в 3:00
0 3 * * * /opt/scripts/backup-docker.sh >> /var/log/docker-backup.log 2>&1

Проверь что скрипт реально отрабатывает — запусти вручную и посмотри файлы в BACKUP_DIR. Скрипт который написан но ни разу не запускался вручную — тоже не бэкап.

Восстановление из бэкапа

Самый важный раздел. Бэкап который не проверен на восстановление — это красивый файл с непонятным содержимым.

Восстановление named volume из tar


# Останови контейнер
docker compose stop postgres

# Очисти существующий volume (если нужно)
docker run --rm \
  -v myproject_pgdata:/target \
  alpine \
  sh -c "rm -rf /target/*"

# Восстанови из архива
docker run --rm \
  -v myproject_pgdata:/target \
  -v /opt/backups:/backup:ro \
  alpine \
  tar xzf /backup/pgdata-20240315-030000.tar.gz -C /target

# Запусти контейнер
docker compose start postgres

Восстановление PostgreSQL из sql.gz


# Для полного дампа через pg_dumpall
gunzip -c /opt/backups/postgres-20240315-030000.sql.gz | \
  docker exec -i myproject_postgres_1 psql -U postgres

# Для дампа конкретной базы через pg_dump
gunzip -c /opt/backups/myapp-20240315-030000.sql.gz | \
  docker exec -i myproject_postgres_1 psql -U postgres myapp

Восстановление bind mount

Если использовал rsync — восстановление тривиальное:


# Останови контейнеры
docker compose down

# Восстанови данные
rsync -avz /opt/backups/myproject/ /opt/apps/myproject/data/

# Подними обратно
docker compose up -d

Миграция volumes между серверами

Переезжаешь на другой сервер или делаешь копию окружения. Схема одна для всех случаев: упаковал, передал, распаковал.

%%{init: {
  'theme': 'base',
  'themeVariables': {
    'primaryColor': '#ffffff',
    'primaryTextColor': '#1e293b',
    'primaryBorderColor': '#94a3b8',
    'lineColor': '#64748b',
    'fontSize': '15px',
    'fontFamily': 'ui-sans-serif, system-ui, sans-serif'
  },
  'flowchart': {'curve': 'linear', 'nodeSpacing': 50, 'rankSpacing': 50}
}}%%
flowchart LR
    A["Старый сервер"] --> B["docker compose down"]
    B --> C["Упаковать в tar.gz"]
    C --> D["scp / rsync на новый сервер"]
    D --> E["Распаковать"]
    E --> F["docker compose up -d"]
    style A fill:#f8fafc,stroke:#3b82f6,stroke-width:2px,color:#1e40af
    style F fill:#f8fafc,stroke:#22c55e,stroke-width:2px,color:#15803d
    style C fill:#f8fafc,stroke:#94a3b8,stroke-width:1px,color:#1e293b
    style D fill:#f8fafc,stroke:#94a3b8,stroke-width:1px,color:#1e293b
    style B fill:#f8fafc,stroke:#94a3b8,stroke-width:1px,color:#1e293b
    style E fill:#f8fafc,stroke:#94a3b8,stroke-width:1px,color:#1e293b

Миграция named volume

На старом сервере:


# Останови стек
docker compose down

# Упакуй volume в tar
docker run --rm \
  -v myproject_pgdata:/source:ro \
  -v /tmp:/backup \
  alpine \
  tar czf /backup/pgdata-migration.tar.gz -C /source .

# Передай на новый сервер
scp /tmp/pgdata-migration.tar.gz user@new-server:/tmp/

На новом сервере:


# Создай volume если ещё нет
docker volume create myproject_pgdata

# Восстанови данные
docker run --rm \
  -v myproject_pgdata:/target \
  -v /tmp:/backup:ro \
  alpine \
  tar xzf /backup/pgdata-migration.tar.gz -C /target

# Скопируй docker-compose.yml и .env
scp user@old-server:/opt/apps/myproject/docker-compose.yml .
scp user@old-server:/opt/apps/myproject/.env .

# Запусти
docker compose up -d

Миграция bind mount

Проще. Копируешь папку целиком:


# На старом сервере - останови и скопируй
docker compose down
rsync -avz -e ssh \
  /opt/apps/myproject/ \
  user@new-server:/opt/apps/myproject/

# На новом сервере
cd /opt/apps/myproject
docker compose up -d

Обрати внимание: rsync копирует и docker-compose.yml, и .env, и data/ одной командой. Вот почему структура каталогов важна.

Системные требования

Компонент Минимальная версия Рекомендуемая
Docker Engine 20.10 26.x
Docker Compose v2.0 v2.24+
ОС Ubuntu 20.04 / Debian 11 Ubuntu 24.04 LTS
Место на диске 10 GB для /var/lib/docker 50+ GB

На момент публикации актуальна версия Docker Engine 26.x. Перед установкой проверь свежие релизы на docs.docker.com.

Осложнения: типичные проблемы и решения

Одна строка в docker-compose.yml без явного указания типа маунта — и через полгода не можешь вспомнить куда вообще пишутся данные. В crontab без комментариев то же самое. Пиши комментарии, будущий ты скажет спасибо.

Ошибка: Permission denied при запуске контейнера

Симптом: контейнер падает сразу после запуска, в логах Permission denied на директорию с данными.

Причина: папка для bind mount создана от root, а процесс внутри контейнера работает от другого пользователя.

Решение:


# Узнай UID процесса внутри контейнера
docker run --rm postgres:16-alpine id postgres
# Выведет: uid=999(postgres) gid=999(postgres)

# Исправь права на хосте
chown -R 999:999 /opt/apps/myproject/data/postgres

Ошибка: volume не найден после docker compose up

Симптом: docker compose up создаёт пустой новый volume вместо подключения к существующему.

Причина: имя volume в docker-compose.yml формируется как project_name_volume_name. Если запускаешь compose из папки с другим именем — получаешь новый volume.


# Посмотри существующие volumes
docker volume ls

# Проверь имя проекта
docker compose config | grep name

# Задай имя проекта явно в docker-compose.yml
name: myproject

Или запускай всегда с явным флагом:


docker compose -p myproject up -d

Ошибка: нет места на диске, /var/lib/docker занял всё

Симптом: No space left on device. Диск занят, но файлов не видно.


# Посмотри что занимает место в Docker
docker system df

# Безопасная очистка без --volumes
docker system prune -f

# Только builder cache
docker builder prune -f

# Только неиспользуемые образы
docker image prune -a -f
Правило безопасной очистки Docker
Никогда не запускай docker system prune —volumes в продакшне без бэкапа и без понимания какие volumes помечены как неиспользуемые. Сначала docker system df, потом принятие решений.

Ошибка: данные потеряны после prune — что делать

Если всё-таки случилось — данные из named volume после docker system prune --volumes восстановить стандартными средствами Docker нельзя. Файлы удаляются на уровне файловой системы.

Шанс есть если:

  • Диск не перезаписывался после удаления (не было интенсивной записи)
  • Есть физический доступ к серверу

# Останови все контейнеры и процессы записи немедленно
docker compose down

# Попробуй extundelete для ext4
apt install extundelete
extundelete /dev/sda1 --restore-directory /var/lib/docker/volumes/

# Или testdisk/photorec для более глубокого восстановления
apt install testdisk
testdisk /dev/sda1

Честно: шансы невысоки. Правильный ответ — настроить бэкап заранее, а не искать инструменты восстановления потом.

Альтернативы: сторонние инструменты бэкапа

Инструмент Плюсы Минусы Подходит для
tar + cron Встроен, просто, надёжно Нет дедупликации, нет шифрования Небольшие данные
restic Дедупликация, шифрование, S3 Надо устанавливать, сложнее Продакшн с большими данными
rsync Быстрый инкрементальный, SSH Нет версионирования само по себе Файлы, uploads
Volumes Backup & Share (плагин) GUI, простой Только Docker Desktop Dev-окружение

Мой выбор для хоумлаба и небольшого продакшна: rsync для файлов + pg_dump для баз + cron. Ничего лишнего, всё понятно, восстановление предсказуемо.

Для серьёзного продакшна с объёмом данных от 50 GB — смотри на restic с бэкендом S3. Дедупликация съекономит место и время передачи.

Профилактика: как не потерять данные

Мониторинг места на диске


# Добавь в crontab проверку места
# Предупреждение если /var/lib/docker занял больше 80%
*/30 * * * * df /var/lib/docker | awk 'NR==2 {if ($5+0 > 80) print "ALERT: Docker disk " $5}' | mail -s "Docker disk alert" admin@example.com

Документируй где лежат данные

Добавь в docker-compose.yml комментарии:


services:
  postgres:
    image: postgres:16-alpine
    volumes:
      # Данные БД: /opt/apps/myproject/data/postgres/
      # Бэкап: /opt/backups/postgres-*.sql.gz (cron 3:00)
      - ./data/postgres:/var/lib/postgresql/data

Проверка бэкапа раз в месяц

Восстанови бэкап в тестовое окружение. Проверь что данные читаются. Убедись что bэкап не битый:


# Проверка целостности архива без распаковки
gzip -t /opt/backups/postgres-20240315-030000.sql.gz && echo "OK" || echo "CORRUPT"

# Проверка sql дампа
gunzip -c /opt/backups/postgres-20240315-030000.sql.gz | head -20

UFW: закрой прямой доступ к данным


# Закрой порт PostgreSQL снаружи
ufw deny 5432

# Открой только для конкретного IP если нужен прямой доступ
ufw allow from 192.168.1.100 to any port 5432

FAQ

Почему docker compose down удаляет данные?

docker compose down без флагов удаляет только контейнеры и сети. Данные в named volumes или bind mount не трогает. Данные теряются если добавить флаг -v или --volumes: тогда удаляются и анонимные volumes объявленные в сервисе. Или если после down запустить docker system prune --volumes. Сами по себе named volumes и bind mount данные переживают любой down.

Как проверить что docker volume работает правильно и данные сохраняются?


# Создай тестовые данные
docker exec postgres_container psql -U postgres -c "CREATE TABLE test (id serial, val text); INSERT INTO test(val) VALUES ('check');"

# Пересоздай контейнер
docker compose down && docker compose up -d

# Проверь что данные на месте
docker exec postgres_container psql -U postgres -c "SELECT * FROM test;"

Если SELECT вернул строку — volume работает. Если таблицы не существует — данные не персистентны, проверь конфиг volumes в docker-compose.yml.

Что делать если docker system prune удалил нужный volume?

Без бэкапа — шансы минимальны. Немедленно останови все процессы записи на диск. Попробуй extundelete или testdisk. Если это продакшн с ценными данными — вызывай специалистов по восстановлению данных, не экспериментируй сам — каждая запись на диск уменьшает шансы. На будущее: настрой автоматический бэкап по инструкции выше.

Чем bind mount отличается от named volume в docker compose?

В bind mount ты указываешь конкретный путь на хосте (./data:/app/data или /opt/data:/app/data) — Docker монтирует эту папку напрямую. В named volume ты указываешь имя (mydata:/app/data) — Docker сам создаёт папку в /var/lib/docker/volumes/ и управляет ею. Bind mount проще контролировать и бэкапить. Named volume лучше для shared volumes между контейнерами и для изоляции от файловой системы хоста.

Можно ли сделать снапшот docker volume без остановки контейнера?

Для файлов — технически да, через LVM snapshot или btrfs snapshot на уровне хоста, если том данных на отдельном LV/subvolume. Для баз данных горячий снапшот файлов небезопасен: данные могут быть в несогласованном состоянии. Используй логический дамп (pg_dump, mysqldump) — он консистентен без остановки. Для Redis с AOF включённым — можно копировать appendonly.aof на работающем инстансе.

Итог

Правило простое: любые данные которые тебе нужны после docker compose down — должны быть либо в bind mount с известным путём, либо в named volume с настроенным бэкапом. Нет третьего варианта.

Прямо сейчас: проверь свои docker-compose.yml файлы. Найди все строки с volumes:. Убедись что знаешь где физически лежат данные каждого сервиса. Настрой cron с бэкапом. Проверь восстановление. Потом спи спокойно.

Если что-то пошло не так
Настроил, запустил, не заработало — пиши в комментарии. Разберёмся. Описывай: версию Docker, что именно делал, что вывела консоль. Без этого — не угадаю.
Андрей Анатольевич
Author: Андрей Анатольевич

Руководитель ИТ / Кризис-менеджер 25 лет в IT: от инженера в МегаФоне до руководителя отдела. Знаю, как выглядит бардак: нестабильные сети, устаревшая инфраструктура, конфликты в команде, раздутые сроки. Помогаю бизнесу выходить из кризиса: навожу порядок в легаси, стабилизирую то, что разваливается, выстраиваю прогнозируемые процессы. Не раз возвращал к жизни ИТ-структуры — знаю цену хаосу. 📍 Ищу проект для полной реорганизации / стабилизации. 📬 Telegram: @over_dude ✉️ mail@it-apteka.com

Оставайтесь на связи

Рецепты от IT-боли. Без воды, без рекламы, без маркетинговой шелухи.

Подписаться на IT-Аптеку →

Мы ВКонтакте

IT-Аптека — советы, новости и помощь рядом.

Вступить в группу ВКонтакте →
Поделитесь:

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Прокрутить вверх