1. Каква е целта
Имам директория с проект, която съдържа:
- Няколко HTML файла (например
index.html,about.html,blog.html) - Директория
js/с JavaScript файлове (напримерmain.js,nav.js) - Възможно е да има и
css/,images/,fonts/
Искам да пусна този статичен сайт в Docker контейнер с Nginx, така че:
- Да работи на локална машина с една команда —
docker compose up - Да се обновява автоматично, когато променя някой файл
- Да мога лесно да го кача на сървър в същия вид
2. Структура на проекта преди Docker
Ето как изглежда проектът ми, преди да добавя каквито и да е Docker файлове:
my-site/
├── index.html
├── about.html
├── blog.html
├── css/
│ └── style.css
├── js/
│ ├── main.js
│ └── nav.js
├── images/
│ └── logo.png
└── fonts/
└── custom.woff2
.htaccess, robots.txt, sitemap.xml. Всички те ще бъдат копирани в контейнера.
3. Стъпка 1: Създаване на Dockerfile
Dockerfile е рецептата, според която Docker изгражда образа на контейнера.
За статичен сайт с Nginx, Dockerfile е изключително кратък:
# Базов образ — минимален Nginx
FROM nginx:alpine
# Копираме всички статични файлове в Nginx директорията
COPY . /usr/share/nginx/html
# Изтриваме дефолтната Nginx страница (по желание)
RUN rm -f /usr/share/nginx/html/index.html
# Nginx слуша на порт 80 по подразбиране
EXPOSE 80
# Стартираме Nginx на преден план
CMD ["nginx", "-g", "daemon off;"]
Нека разгледам какво прави всяка команда:
FROM nginx:alpine— използвам официалния Nginx образ на базата на Alpine Linux. Alpine е минимална дистрибуция — образът е около 25 MB.COPY . /usr/share/nginx/html— копирам всичко от текущата директория в директорията, от която Nginx сервира файлове.RUN rm -f /usr/share/nginx/html/index.html— премахвам дефолтната Nginx страница. Ако моят проект имаindex.html, тя ще я замени. Този ред не е задължителен.EXPOSE 80— декларирам, че контейнерът слуша на порт 80. Това е само документация, не отваря порта към външния свят — това става вdocker-compose.yml.CMD ["nginx", "-g", "daemon off;"]— стартирам Nginx на преден план.daemon off;е задължителен, за да не спира контейнерът след стартиране.
.dockerignore (ще създадем такъв по-нататък), трябва да внимавам да не изключа важни файлове. COPY . копира всичко, което не е изключено.
4. Стъпка 2: Създаване на .dockerignore
Когато копирам всичко с COPY ., не искам в контейнера да попаднат:
- Git директории (
.git/) - Node модули (
node_modules/) — ако проектът използва npm - Локални конфигурационни файлове (
.env,.vscode/) - Docker файлове (
Dockerfile,docker-compose.yml) — нямат място в статичния сайт
Създавам .dockerignore в корена на проекта:
# Git
.git/
.gitignore
# Docker
Dockerfile
docker-compose.yml
.dockerignore
# Node.js
node_modules/
npm-debug.log
# Редактори
.vscode/
.idea/
*.swp
# Локални конфигурации
.env
.env.local
# Други временни файлове
tmp/
temp/
*.log
Този файл спестява време при изграждане на образа и намалява размера му.
5. Стъпка 3: Създаване на docker-compose.yml
С docker-compose.yml описвам как да стартирам контейнера. Дори за един контейнер,
използвам docker-compose, защото:
- Мога да монтирам файловете като volume за автоматично обновяване
- Мога лесно да добавя други контейнери по-късно (например база данни)
- Командата за стартиране е кратка и запомняща се
version: '3.8'
services:
web:
build:
context: .
dockerfile: Dockerfile
container_name: my-site
ports:
- "8080:80"
volumes:
- ./:/usr/share/nginx/html
restart: unless-stopped
Разглеждам всяка част:
version: '3.8'— версия на Docker Compose формата.3.8е съвместима с всички съвременни версии на Docker.services:— списък на контейнерите.web:— име на услугата. Мога да я назова както искам.build:— казва на Docker Compose да изгради образ отDockerfileв текущата директория (context: .).container_name: my-site— име на контейнера. Удобно е заdocker execиdocker logs.ports:—"8080:80"означава: порт 8080 на хоста се пренасочва към порт 80 в контейнера. Отварям браузър наhttp://localhost:8080.volumes:— монтирам текущата директория (./) върху/usr/share/nginx/htmlв контейнера. Това означава, че всяка промяна в моите файлове се отразява моментално в контейнера, без да се налага преизграждане.restart: unless-stopped— контейнерът се рестартира автоматично при спиране, освен ако не е спрян изрично. Полезно за сървър.
volumes.
Благодарение на него, променям HTML или JS файл, обновявам браузъра и виждам промяната веднага.
Няма нужда от docker build след всяка редакция.
6. Стъпка 4: Изграждане и стартиране
След като имам трите файла (Dockerfile, .dockerignore, docker-compose.yml),
мога да изградя и пусна контейнера.
Първо изграждане на образа
docker compose build
Тази команда чете Dockerfile и създава образ. Първият път може да отнеме няколко секунди,
защото Docker трябва да изтегли nginx:alpine.
Стартиране на контейнера
docker compose up
Това стартира контейнера и показва логовете в терминала. Натискам Ctrl+C, за да спра.
Стартиране във фонов режим
docker compose up -d
-d означава detached — контейнерът работи на заден план. Мога да видя логовете с:
docker compose logs -f
След стартиране, сайтът е достъпен на http://localhost:8080.
7. Стъпка 5: Проверка дали работи
Имам няколко начина да проверя дали контейнерът работи коректно:
Проверка на състоянието
docker compose ps
Проверка на файловете вътре в контейнера
docker exec -it my-site ls -la /usr/share/nginx/html
Директно отваряне на файл в контейнера
docker exec -it my-site cat /usr/share/nginx/html/index.html
Проверка на HTTP отговор
curl -I http://localhost:8080
8. Стъпка 6: Автоматично обновяване при промяна
Благодарение на volumes: - ./:/usr/share/nginx/html, всяка промяна в моя проект
се отразява веднага в контейнера. Nginx обаче кешира статичните файлове, така че понякога
трябва да му кажа да презареди конфигурацията.
Има няколко начина:
Рестартиране на контейнера
docker compose restart web
Презареждане на Nginx конфигурация без рестарт
docker exec -it my-site nginx -s reload
Автоматично презареждане при промяна на файлове
Ако искам напълно автоматично обновяване, мога да използвам инструмент за синхронизация
като watch или inotifywait. Един прост вариант:
while inotifywait -r -e modify .; do
docker exec -it my-site nginx -s reload
done
Това следи за промени в текущата директория и при всяка промяна презарежда Nginx.
Изисква инсталиран inotify-tools.
9. Стъпка 7: Качване на Docker образа в регистър
След като съм доволен от локалната работа, мога да кача образа в Docker Hub или друг регистър, за да го ползвам на сървър.
Изграждане на образ с таг
docker build -t myusername/my-site:latest .
Влизане в Docker Hub
docker login
Качване на образа
docker push myusername/my-site:latest
След като образът е качен, мога да го използвам на всеки сървър, който има Docker, с една единствена команда:
docker run -p 8080:80 -d myusername/my-site:latest
10. Стъпка 8: Пускане на сървър с Docker Compose
На сървъра не искам да монтирам локалната директория като volume — искам да използвам
готовия образ. Затова на сървъра docker-compose.yml изглежда малко по-различно:
version: '3.8'
services:
web:
image: myusername/my-site:latest
container_name: my-site-production
ports:
- "80:80"
restart: unless-stopped
Разликите:
- Няма
build— използвам готов образ от регистъра. - Няма
volumes— не искам промените на сървъра да влияят на контейнера. - Портът е
"80:80"— сайтът е достъпен директно на порт 80.
Пускам го със същата команда:
docker compose up -d
11. Стъпка 9: Добавяне на собствена Nginx конфигурация
Понякога дефолтната Nginx конфигурация не е достатъчна. Искам да добавя:
- Кастомна страница за грешка 404
- Компресия (gzip)
- Кеширане на статични файлове
- Пренасочване от HTTP към HTTPS
За целта създавам nginx.conf в проекта:
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
# Компресия
gzip on;
gzip_types text/html text/css application/javascript;
# Кеширане на статични файлове
location ~* \.(jpg|jpeg|png|gif|ico|svg|woff2|css|js)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
# Кастомна страница за 404
error_page 404 /404.html;
location = /404.html {
internal;
}
# Пренасочване на всички останали заявки към index.html
location / {
try_files $uri $uri/ =404;
}
}
След това променям Dockerfile, за да копирам тази конфигурация:
FROM nginx:alpine
# Копираме собствения nginx.conf
COPY nginx.conf /etc/nginx/conf.d/default.conf
# Копираме статичните файлове
COPY . /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
default.conf
и да бъде в /etc/nginx/conf.d/. Това е директорията, от която Nginx автоматично
зарежда конфигурации.
12. Стъпка 10: Добавяне на Node.js или друг backend
Ако проектът ми не е само статичен, а има и бекенд (например Node.js с Express),
мога да добавя втори контейнер в docker-compose.yml:
version: '3.8'
services:
web:
build:
context: .
dockerfile: Dockerfile
container_name: my-site-web
ports:
- "80:80"
volumes:
- ./:/usr/share/nginx/html
depends_on:
- api
api:
build:
context: ./api
dockerfile: Dockerfile
container_name: my-site-api
ports:
- "3000:3000"
environment:
- NODE_ENV=production
restart: unless-stopped
Тук depends_on гарантира, че API контейнерът стартира преди уеб контейнера.
Уеб контейнерът може да комуникира с API през името на услугата api.
13. Стъпка 11: Добавяне на environment променливи
Често имам нужда от environment променливи — например API ключове, режим на работа,
портове. В docker-compose.yml ги задавам така:
services:
web:
image: myusername/my-site:latest
environment:
- NODE_ENV=production
- API_URL=https://api.example.com
- SECRET_KEY=${SECRET_KEY} # от .env файл
Мога да използвам и .env файл в същата директория:
SECRET_KEY=supersecret123
API_URL=https://api.example.com
.gitignore. Вместо това създавам .env.example
с примерни стойности.
14. Стъпка 12: Полезни команди за управление
Ето няколко команди, които използвам постоянно:
| Команда | Какво прави |
|---|---|
docker compose up -d | Стартира контейнерите във фонов режим |
docker compose down | Спира и премахва контейнерите |
docker compose logs -f | Следва логовете на всички контейнери |
docker compose logs -f web | Следва логовете само на web |
docker compose ps | Показва състоянието на контейнерите |
docker compose exec web sh | Отваря shell в контейнера web |
docker compose build --no-cache | Изгражда образите без кеш (пълно преизграждане) |
docker system prune -a | Изтрива неизползвани образи, контейнери и сети |
15. Често срещани проблеми и решения
Портът 8080 е зает
ports:
- "8081:80" # промяна на хоста от 8080 на 8081
Файловете не се обновяват в контейнера
- Проверявам дали volume-то е монтирано правилно:
docker inspect my-site | grep Mounts - Проверявам дали файловете са в правилната директория
- Рестартирам контейнера:
docker compose restart web
Nginx връща 404
- Проверявам дали
index.htmlсъществува в/usr/share/nginx/html - Проверявам дали
try_filesв конфигурацията е правилен - Проверявам логовете на Nginx:
docker exec -it my-site cat /var/log/nginx/error.log
Образът е твърде голям
- Използвам
nginx:alpineвместоnginx:latest - Добавям
.dockerignore, за да изключа ненужни файлове - Използвам multi-stage build, ако проектът изисква компилация
16. Обобщение на файловете
Ето как изглежда цялата структура на проекта след всички стъпки:
my-site/
├── index.html
├── about.html
├── blog.html
├── 404.html
├── css/
│ └── style.css
├── js/
│ ├── main.js
│ └── nav.js
├── images/
│ └── logo.png
├── fonts/
│ └── custom.woff2
├── nginx.conf # собствена Nginx конфигурация
├── Dockerfile # рецепта за изграждане на образа
├── docker-compose.yml # оркестрация на контейнерите
├── .dockerignore # какво да не се копира в образа
├── .env # environment променливи (не се качва в Git)
├── .env.example # примерни environment променливи
└── .gitignore # какво да не се качва в Git
17. Заключение
С тези стъпки превръщам всяка директория с HTML и JS файлове в напълно контейнеризирано приложение, което мога да пусна навсякъде, където има Docker.
Процесът е:
рецепта
изключваме излишното
стартиране
работи