Linux — практическо ръководство

От HTML и JS до Docker Compose — стъпка по стъпка

Имам няколко HTML файла, няколко JS файла и искам да ги пусна в Docker контейнер. В тази статия описвам целия процес — от създаване на Dockerfile до работещ docker-compose.yml с Nginx и автоматично обновяване при промяна.

ℹ Бележка: Това не е теория за Docker — това е реалният ми процес. Всеки път, когато започвам нов проект, който искам да пусна локално или на сървър, правя точно тези стъпки. Всичко е тествано на реални файлове.

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 е изключително кратък:

Dockerfile за статичен сайт с NginxКопирай

# Базов образ — минимален 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 в корена на проекта:

.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 за автоматично обновяване
  • Мога лесно да добавя други контейнери по-късно (например база данни)
  • Командата за стартиране е кратка и запомняща се

docker-compose.yml за статичен сайтКопирай

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Копирай

docker compose build

Тази команда чете Dockerfile и създава образ. Първият път може да отнеме няколко секунди, защото Docker трябва да изтегли nginx:alpine.

Стартиране на контейнера

docker compose upКопирай

docker compose up

Това стартира контейнера и показва логовете в терминала. Натискам Ctrl+C, за да спра.

Стартиране във фонов режим

docker compose up -dКопирай

docker compose up -d

-d означава detached — контейнерът работи на заден план. Мога да видя логовете с:

Преглед на логовеКопирай

docker compose logs -f

След стартиране, сайтът е достъпен на http://localhost:8080.

7. Стъпка 5: Проверка дали работи

Имам няколко начина да проверя дали контейнерът работи коректно:

Проверка на състоянието

docker compose psКопирай

docker compose ps

Проверка на файловете вътре в контейнера

docker exec — преглед на файловетеКопирай

docker exec -it my-site ls -la /usr/share/nginx/html

Директно отваряне на файл в контейнера

docker exec — преглед на index.htmlКопирай

docker exec -it my-site cat /usr/share/nginx/html/index.html

Проверка на HTTP отговор

curl към контейнераКопирай

curl -I http://localhost:8080

8. Стъпка 6: Автоматично обновяване при промяна

Благодарение на volumes: - ./:/usr/share/nginx/html, всяка промяна в моя проект се отразява веднага в контейнера. Nginx обаче кешира статичните файлове, така че понякога трябва да му кажа да презареди конфигурацията.

Има няколко начина:

Рестартиране на контейнера

docker compose restartКопирай

docker compose restart web

Презареждане на Nginx конфигурация без рестарт

docker exec — reload на NginxКопирай

docker exec -it my-site nginx -s reload

Автоматично презареждане при промяна на файлове

Ако искам напълно автоматично обновяване, мога да използвам инструмент за синхронизация като watch или inotifywait. Един прост вариант:

Автоматично презареждане с inotifywaitКопирай

while inotifywait -r -e modify .; do
docker exec -it my-site nginx -s reload
done

Това следи за промени в текущата директория и при всяка промяна презарежда Nginx. Изисква инсталиран inotify-tools.

ℹ Бележка: За локална разработка: обикновено ми е достатъчно да рестартирам контейнера ръчно при промяна на важни файлове. За HTML и JS, които не се кешират агресивно, често е достатъчно само да обновя браузъра.

9. Стъпка 7: Качване на Docker образа в регистър

След като съм доволен от локалната работа, мога да кача образа в Docker Hub или друг регистър, за да го ползвам на сървър.

Изграждане на образ с таг

docker build с тагКопирай

docker build -t myusername/my-site:latest .

Влизане в Docker Hub

docker loginКопирай

docker login

Качване на образа

docker pushКопирай

docker push myusername/my-site:latest

След като образът е качен, мога да го използвам на всеки сървър, който има Docker, с една единствена команда:

docker run на качен образКопирай

docker run -p 8080:80 -d myusername/my-site:latest

10. Стъпка 8: Пускане на сървър с Docker Compose

На сървъра не искам да монтирам локалната директория като volume — искам да използвам готовия образ. Затова на сървъра docker-compose.yml изглежда малко по-различно:

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Копирай

docker compose up -d

11. Стъпка 9: Добавяне на собствена Nginx конфигурация

Понякога дефолтната Nginx конфигурация не е достатъчна. Искам да добавя:

  • Кастомна страница за грешка 404
  • Компресия (gzip)
  • Кеширане на статични файлове
  • Пренасочване от HTTP към HTTPS

За целта създавам nginx.conf в проекта:

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, за да копирам тази конфигурация:

Dockerfile със собствена Nginx конфигурацияКопирай

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:

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 ги задавам така:

docker-compose.yml с environmentКопирай

services:
web:
image: myusername/my-site:latest
environment:
- NODE_ENV=production
- API_URL=https://api.example.com
- SECRET_KEY=${SECRET_KEY}  # от .env файл

Мога да използвам и .env файл в същата директория:

.env файлКопирай

SECRET_KEY=supersecret123
API_URL=https://api.example.com

⚠ Предупреждение: Никога не качвам .env файлове в Git. Добавям ги в .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. Обобщение на файловете

Ето как изглежда цялата структура на проекта след всички стъпки:

Файлова структура с DockerКопирай

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.

Процесът е:

1. Dockerfile

рецепта

→

2. .dockerignore

изключваме излишното

→

3. docker-compose.yml

стартиране

→

4. docker compose up

работи

✓: Моят принцип: всеки проект, който поддържам, получава Dockerfile и docker-compose.yml от самото начало. Това ми спестява часове при прехвърляне между машини и при деплой на сървър. Добавянето на Nginx конфигурация ми дава контрол върху кеширането, компресията и грешките.