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 конфигурация ми дава контрол върху кеширането, компресията и грешките.