Рассказывать про все команды Dockerfile
и все опции docker
нет никакого смысла — на это есть документация на
официальном сайте. Однако, хочется
рассказать про небольшое количество самых полезны в быту
опций и возможностей.
Давайте, я опишу решение вполне бытовой задачи: я решил пройти курсы по ruby. Ставить в систему всё необходимое я не хочу. По многим причинам: я не уверен, что ruby мне понадобится для чего-то кроме выполнения заданий по курсам (не хочется захламлять систему); возможно, выяснится, что нужны какие-то определённые версии (не хочется захламлять систему и мучиться с даунрейдами); наверняка захочется покопаться с стандартных библиотеках, наставлять туда дебага, хочется иметь возможность легко и гарантированно восстановить всё в первозданном виде.
Я предполагаю, что вы прочитал мою первую заметку про docker
Итак, чего мы хотим получить:
ruby
, rails
, sqlite3
и сопутствующими утилитамиsudo
Dockerfile получился таким:
FROM ubuntu
RUN apt-get update &&\
DEBIAN_FRONTEND=noninteractive \
apt-get install -y \
sudo \
screen \
mc \
vim \
ruby \
ruby-rails \
sqlite3 \
curl \
&&\
addgroup --system a &&\
adduser --system --ingroup a --disabled-password a &&\
\
echo 'a ALL=(ALL:ALL) NOPASSWD:ALL' >>/etc/sudoers &&\
\
echo 'caption always "%{= bb}%{+b w}%n %h %=%t %c"' >>~a/.screenrc &&\
echo 'hardstatus alwayslastline "%-Lw%{= BW}%50>%n%f* %t%{-}%+Lw%<"' >>~a/.screenrc &&\
\
echo "export PS1='\001\033[32;1m\002[\u \W]\$\001\033[0m\002 '" >>~a/.bashrc &&\
echo "export LANG=C.utf8" >>~a/.bashrc &&\
echo "export EDITOR=/usr/bin/vim" >>~a/.bashrc &&\
echo "export VIEWER=/usr/bin/vim" >>~a/.bashrc &&\
echo "export PAGER=/usr/bin/less" >>~a/.bashrc &&\
echo "export LESS=FRSXQ" >>~a/.bashrc &&\
echo "export LESSCHARSET=utf-8" >>~a/.bashrc &&\
echo "export GREP_COLORS='fn=36:ms=01;32'" >>~a/.bashrc &&\
echo "alias ls='ls --color=auto'" >>~a/.bashrc &&\
\
echo ". ~/.bashrc" >>~a/.bash_profile &&\
\
true
CMD ["su", "-l", "a", "-s", "/bin/bash"]
Что тут есть интересного, на что стоит обратить внимание. Пойдём по прядку.
DEBIAN_FRONTEND=noninteractive
очень полезная переменная окружения,
которую часто забывают. Она предотвращает любые диалоги с пользователем
во время установки пакетов (-y
отключает только подтверждение).
В частности, наш набор пакетов потребует установки tzdata
, а его
установка сопровождается вопросами про то, в какой временной зоне
вы находитесь.
Дальше ничего необычного:
sudo
без пароляscreen
Здесь вы можете сделать всё, как нравится вам.
Стоит только пояснить, почему всё сделано в одной команде RUN
.
Так делать не обязательно. У вас всегда есть выбор из двух опций:
RUN
: при этом, на каждый RUN
docker
создаёт отдельный слой файловой системы. Т.е. если вы измените одну команду в отдельном RUN
, то docker build
пресоберёт только этот слой, остальные будут взяты от предыдущей сборки. Чем это хорошо: (i) пересборки после мелких исправлений происходят быстрее; (ii) результаты многих исправлений хранятся компактней.RUN
: при этом создаётся только один слой. Если вы меняете одну команду, то docker-у для сборки придётся перевыполнить весь набор команд. Плюс здесь только в одном: меньше слоёв.Я использую второй подход, так как преимущества первого подхода мне не нужны: я не делаю много пересборок, а если и делаю, то хранить неудачные варианты мне не нужно.
Тут рассказывать особо нечего. Я просто запускаю shell для пользователя, командой
su -l a -s /bin/bash
sudo docker build -t ror:latest .
Параметры его получаются примерно такими:
$ sudo docker image ls -a
REPOSITORY TAG IMAGE ID CREATED SIZE
ror latest c96751e9cce9 1 hours ago 342MB
Вот тут есть что обсудить:
sudo docker run -it -v /tmp:/home/a/w -p 3000:3000 --name ror-vm ror
Тут есть две полезных опции:
-v /tmp:/home/a/w
: монтирует директорию хост-системы внутрь контейнера. Т.е. теперь, всё, что в контейнере лежит в /home/a/w
, будет оказываться в хост системе в /tmp
(и наоборот). Это очень удобно: для сохранения ваших изменений (проделанных в этой директории) не нужно делать docker commit
. Вы можете вообще удалить этот docker-образ, сделать новый… не важно, — эта часть файловой системы отображается на вашу обычную файловую систему.-p 3000:3000
: проброс порта. Дело обычное, но немного замечаний на эту тему будет ниже.После выполнения этой команды, вы получите shell пользователя (не root) внутри контейнера.
Не забывайте, что если вы произведёте какие-то изменения в контейнере,
то они не сохранятся при перезапуске. Чтобы их сохранить, надо или
создать новый скорректированный образ командой docker commit
, или
внести ваши действия в Dockerfile
и пересобрать весь образ.
Однако, нередко именно полный ресет системы и изначальному состоянию — это как раз то, что нужно.
Итак вы запустили docker на вашем образе, получили shell.
$ cd w
$ rails new firsttest
$ cd firsttest/
$ rails s -b 0.0.0.0
Тут существенно, что
cd w
),-b 0.0.0.0
), а не на localhost
. Только так он будет виден снаружи контейнера.Теперь на вашей хост-системе открываем в браузере URL http://localhost:3000/
и убеждаемся, что всё работает.
Теперь вы можете редактировать файлы проекта и на хост-системе, и в самом docker-контейнере (там для
этого есть screen
и vim
).
Вместо su
можно использовать инструкцию Docker USER
. Единственное,
на что надо будет обратить внимание, что shell запускался в login-режиме.
Файлы на внешней файловой системе будут создаваться с правами внутреннего пользователя. Если вы с ними будете работать только из контейнера, то вы этого, скорее всего, не заметите. Но если вы хотите работать с ними и локально, то имеет смысл создать пользователя и группу с теми же цифровыми id, что и в вашей хост-системе. Тут не всё так просто, скорее всего вы получите конфликт номеров групп, а может даже и конфликт uid-ов. Как их решать (и надо ли их решать) — зависит от ваших конкретных задач.
Если вы захотите сделать гибкую настройку группы и пользователя, то
вам, скорее сего, захочется параметризоваться Dockerfile
. Тут вам
поможет директива ARG
.
Но тут вы можете столкнуться с тем, что переменные не всегда ведут себя очевидным образом в shell. Например, наблюдается такой парадокс:
$ echo ~a
/Users/a
$ x=a
$ echo ~$x
~a
Решаются подобные штуки через eval
:
$ eval echo ~$x
/Users/a
Но тут надо не забывать кавычки.
Если вы хотите настроить time zone, добавьте явно:
ln -sf /usr/share/zoneinfo/GMT /etc/localtime &&\
Если вы действительно захотите создавать такой контейнер ради Ruby on rails,
и если вы собираетесь использовать vim
, вам могут пригодится такие команды:
echo "set ai" >>/etc/vim/vimrc &&\
echo "set nu" >>/etc/vim/vimrc &&\
echo ":autocmd Filetype ruby set sw=2" >>/etc/vim/vimrc &&\
echo ":autocmd Filetype ruby set ts=2" >>/etc/vim/vimrc &&\
echo ":autocmd Filetype ruby set softtabstop=2" >>/etc/vim/vimrc &&\
Успехов!