Від локальної розробки до production: як підготувати вебзастосунок до розгортання

Від локальної розробки до production: як підготувати вебзастосунок до розгортання

Локально застосунок може запускатися з першої команди, бачити базу даних, знаходити всі пакети й коректно звертатися до API. Після перенесення на сервер картина інколи змінюється за кілька хвилин: збірка падає, процес не стартує, Nginx повертає 502, а в журналах з’являються помилки доступу до файлів або підключення до БД.

Production — окреме середовище, де мають збігтися версії runtime, залежності, змінні оточення, схема БД, права доступу, порти, reverse proxy і спосіб запуску процесу. Надійніше перевіряти цей ланцюжок послідовно, а не перезапускати все підряд.

Нижче — практичний шлях від репозиторію до робочого production із командами та симптомами, за якими можна швидко зрозуміти, на якому рівні зламався deploy.

Чому застосунок, який працює локально, може не запуститися на сервері

Найчастіше проблема не в самому коді, а у відмінностях між локальним і production-середовищем. На робочому комп’ютері вже можуть бути встановлені бібліотеки, глобальні пакети, потрібна версія Node.js або Python, локальна база даних і змінні середовища, про які розробник давно не замислюється. На чистому сервері цього немає.

Почати варто з порівняння середовищ. Для Node.js перевіряють node --versionіnpm --version, для Python — python --version, для PHP — php -v. Змінні оточення можна переглянути через env або printenv, а зайняті порти — через ss -lntp. Якщо вже тут версії розходяться, копати Nginx поки немає сенсу.

Окрема класика — різниця між файловими системами. На Linux застосунок може впасти через регістр у назві файлу: імпорт ./UserService не знайде userservice.js. Так само локальний запуск інколи тримається на глобальному пакеті, якого немає в package.json,requirements.txt,pyproject.toml або composer.json.

Ще один частий випадок — жорстко прописаний localhost. У локальній конфігурації він може вести до API або БД на тому самому комп’ютері, а в production — уже не туди, куди очікує застосунок. Спочатку вирівнюємо середовище, а вже потім шукаємо помилку в коді.

Що потрібно зафіксувати перед першим deploy

Якщо незрозуміло, що саме потрібно встановити на чистому сервері, проєкт ще не повністю готовий до production. Нормальний deploy можна повторити на новій машині без здогадок на кшталт «здається, тут ще потрібен якийсь глобальний пакет».

Версії runtime та залежностей

Версію runtime краще фіксувати явно. Для Node.js це може бути .nvmrc або поле engines, для Python — файл залежностей чи Poetry, для PHP — composer.json разом із composer.lock. Lock-файл зменшує ризик, що production отримає інші версії пакетів, ніж середовище, де код тестували.

Для Node.js у production зазвичай доречніше npm ci, а не довільний npm install, якщо lock-файл актуальний. Саме тут часто й вилізає залежність, яка на ноутбуці роками жила глобально. В інших стеках принцип той самий: сервер має встановити описаний набір залежностей, а не випадково сумісний.

Змінні середовища і секрети

Production-конфігурація не повинна жити в репозиторії разом із реальними паролями, API-ключами, токенами, SMTP-доступами або JWT-secret. Зручно зберігати шаблон .env.example, який показує, які змінні потрібні, але не містить секретних значень.

APP_ENV=production
APP_URL=https://example.com
DB_HOST=127.0.0.1
DB_NAME=app
DB_USER=app_user

Якщо після клонування репозиторію незрозуміло, які змінні треба задати, документація неповна. Чистий deploy добре показує, чи можна відтворити середовище без ручних «магічних» дій.

Build-команди та production-конфігурація

Потрібно заздалегідь знати, що саме вважається production-збіркою. Для фронтенду це може бути npm run build, для backend — окрема команда старту, набір міграцій або підготовка статичних файлів. Development server, який зручно використовувати локально, не завжди придатний для постійного публічного запуску.

Яке серверне середовище потрібне для production

Серверне середовище варто обирати за архітектурою застосунку, а не тільки за диском і обсягом трафіку. Статичному frontend потрібен один рівень контролю, типовому PHP-сайту — інший, а застосунку з Node.js, Python, Redis, worker-процесами, WebSocket або Docker Compose — зовсім інший.

Коли достатньо звичайного хостингу

Якщо застосунок не потребує власних системних служб, окремих портів чи спеціальної мережевої конфігурації, shared hosting може бути достатнім. Для статичних сайтів або стандартних PHP-проєктів піднімати окремий сервер лише «бо production» часто немає сенсу.

Коли потрібен контроль над операційною системою

Якщо застосунок потребує власних системних служб, окремих версій Node.js або Python, Docker, налаштування firewall чи reverse proxy, можливостей звичайного shared hosting може бути недостатньо. У такому випадку для production-середовища часто використовують Linux VDS, де можна самостійно керувати операційною системою, мережевими правилами та серверними службами.

Сам по собі VDS не робить застосунок стабільним. Разом із контролем з’являється відповідальність за оновлення системи, firewall, служби, резервні копії, журнали й моніторинг. Якщо такого контролю проєкт не потребує, зайве адміністрування лише додає точок відмови.

Які ресурси важливі на старті

free -h покаже використання пам’яті, df -h— заповнення файлових систем, nproc— кількість доступних CPU, а ss -lntp — процеси, що слухають порти. Але це ще не профіль навантаження. Щоб побачити, чи сервер реально впирається в CPU або RAM, дивляться uptime,top чи vmstat; для контейнерів корисний docker stats.

Root-доступ, Docker, Redis, worker-процеси, власні системні бібліотеки, WebSocket або нестандартні порти — аргументи на користь середовища з повним контролем. Якщо таких вимог немає, інфраструктуру не варто ускладнювати наперед.

Як правильно перенести код, залежності та базу даних

Deploy — це не просто git pull. Нова версія коду має бути синхронізована із залежностями, production-конфігурацією та схемою бази даних. Якщо хоча б одна частина відстає, процес може успішно стартувати, але падати вже на реальних запитах.

Код і залежності

Робоча послідовність: отримати потрібний commit, встановити залежності з lock-файлу, виконати production build, застосувати міграції, перезапустити процес і провести smoke-test. Версію коду корисно фіксувати через git rev-parse HEAD — це точніше, ніж «ми наче залили останню».

Для Node.js після оновлення репозиторію доречно виконати npm ciіnpm run build. Для PHP-проєктів часто використовують composer install --no-dev. Команди відрізняються за стеком, але production не повинен отримувати випадковий набір залежностей.

Міграції бази даних

Код і схема БД мають їхати в production як одна версія. Наприклад, новий backend уже звертається до поля, якого немає у старій схемі. Сам процес при цьому може стартувати без помилок, а збій проявиться лише під час конкретного API-запиту.

Перед критичними змінами БД потрібна актуальна резервна копія. Після міграцій варто перевірити їхній статус засобами Laravel, Django, Prisma, Sequelize чи інструмента свого стеку.

СимптомЩо перевірити насамперед
Module not foundЧи встановлено залежності за актуальним lock-файлом
Build працює локально, але падає на серверіВерсію runtime, RAM і production-змінні середовища
Таблиця або колонка не існуєСтатус міграцій БД
Застосунок бачить старі або тестові даніDB_HOST, DB_NAME і активне production-середовище

Окремо визначають дані, які не можна перезаписувати під час deploy. Каталоги uploads, файлове сховище користувачів, volumes контейнерів і production-БД мають зберігатися між релізами, а не оновлюватися разом із кодом.

Чому застосунок не варто залишати запущеним просто з SSH

Вебзастосунок, який перестає працювати після закриття SSH, запущений не як керована служба. Команда npm start або python app.py у терміналі підходить для перевірки, але не забезпечує автоматичний старт після reboot, відновлення після падіння чи нормальну роботу з журналами.

systemd, PM2 або restart policy

У Linux типовий варіант — systemd. Для Node.js також використовують PM2, а в контейнерній схемі — restart policy Docker. Тут інструмент уже другорядний: важливо, щоб процес стартував без відкритого SSH-сеансу, працював від окремого користувача й сам піднімався після збою.

[Service]
User=app
WorkingDirectory=/srv/myapp
EnvironmentFile=/srv/myapp/.env
ExecStart=/usr/bin/node server.js
Restart=always

Шлях до runtime в ExecStart треба перевірити саме на сервері. Якщо Node.js встановлений через nvm або іншим способом, він може бути не в /usr/bin/node; швидка перевірка — which node.

systemctl status myapp
systemctl enable myapp
journalctl -u myapp -n 100
journalctl -u myapp -f

Якщо production тримається на відкритому терміналі — це ще не production. Простий тест: запустити службу, закрити SSH і перевірити сайт. Потім переконатися, що сервіс піднімається після reboot або перезапуску контейнера.

Як поставити Nginx перед застосунком і відкрити його через HTTPS

Якщо backend відповідає на 127.0.0.1:3000, але домен не відкривається, проблема вже не обов’язково в самому застосунку. У типовій схемі внутрішній порт не публікують напряму: запит проходить шлях браузер → DNS → 80/443 → Nginx → внутрішній порт застосунку.

Reverse proxy

Фрагмент server-блоку Nginx може виглядати так:

server_name example.com;

location / {
    proxy_pass http://127.0.0.1:3000;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
}

Після зміни конфігурації запускають nginx -t, а якщо синтаксис коректний — systemctl reload nginx. Застосунок перед цим перевіряють GET-запитом: curl -i http://127.0.0.1:3000/ або через /health, якщо такий endpoint є.

Для Nginx помилка 502 Bad Gateway найчастіше означає, що reverse proxy не може отримати коректну відповідь від application-сервера. Тому 502 зручніше розбирати від upstream назад: systemctl status myappss -lntpcurl на локальний порт → error log Nginx. Є відповідь на localhost — чудово, процес живий. Тепер коло звузилося до proxy, TLS, DNS або мережі.

DNS та HTTPS

Домен повинен вказувати на потрібну IP-адресу, що можна перевірити через dig example.com. Для HTTPS часто використовують Let’s Encrypt і Certbot, але після видачі сертифіката потрібно перевірити саме публічну адресу: curl -I https://example.com. Помилка сертифіката може означати не збій застосунку, а неправильний домен, virtual host або конфігурацію TLS.

WebSocket, великі uploads або довгі API-запити можуть потребувати додаткових proxy-параметрів. Їх краще додавати за потребою, а не копіювати «універсальний» конфіг, половина якого проєкту не стосується.

Права доступу, firewall і секрети: помилки, які часто з’являються вже після deploy

chmod 777 зазвичай маскує причину Permission denied, а не виправляє її. Якщо після 777 застосунок раптом почав писати в uploads або cache, це лише підтверджує, що проблема пов’язана з правами. Далі треба з’ясувати, від якого користувача працює процес і кому належить каталог.

Перевірка починається з ls -la. Потім за потреби виправляють owner через chown і задають мінімально достатні права через chmod. Запускати застосунок від root без потреби — погана заміна нормальній моделі доступу. Тут правило коротке: спочатку owner, потім права.

Те саме стосується мережі. Якщо backend слухає внутрішній порт 3000, це не означає, що його потрібно відкривати для всього Інтернету. Зовні зазвичай достатньо SSH та 80/443, а застосунок, Redis чи БД можуть залишатися доступними тільки локально або в приватній мережі.

Активні порти перевіряють через ss -lntp, правила UFW — через ufw status. Якщо використовується nftables або інший firewall, принцип не змінюється: відкритим має бути тільки те, що справді потрібно публічному трафіку.

Production-паролі, токени, ключі API та .env не повинні опинятися в Git. Для CI/CD секрети краще передавати через захищене сховище змінних, а не вставляти в pipeline як звичайний текст.

Як перевірити production після deploy і швидко знайти проблему

Production найшвидше перевіряти зсередини назовні: процес → локальний порт → reverse proxy → HTTPS → браузер. Так знаходять перший рівень, на якому зникає відповідь, замість хаотичних перезапусків.

1. Перевірити процес

Для systemd це systemctl status myapp, для Docker — docker ps, для PM2 — pm2 status. Якщо процес уже впав, браузер і DNS поки що не мають значення. Причину шукають у журналі застосунку або journalctl.

2. Перевірити локальний HTTP

Далі дивляться, чи слухається очікуваний порт через ss -lntp, і звертаються до застосунку напряму:

curl http://127.0.0.1:3000/health

Якщо health endpoint відповідає, процес живий. Якщо ні — проблема ще всередині застосунку, його конфігурації, БД або залежностей.

3. Перевірити Nginx, домен і HTTPS

Потім перевіряють nginx -t, стан Nginx і запит через публічний домен. Якщо localhost працює, а через домен отримуємо 502, дивляться proxy_pass, порт upstream і error log Nginx. Якщо відповідь не доходить навіть до сервера, тоді вже перевіряють DNS, firewall і мережевий маршрут.

4. Перевірити не тільки головну сторінку

Успішний build ще не означає успішний реліз. Треба перевірити хоча б одну ключову операцію: авторизацію, запис у БД, upload, API-запит, відправлення email або фонову задачу — залежно від застосунку.

Якщо після deploy HTML уже новий, а JavaScript чи CSS старі, перевіряють назви зібраних файлів, browser/CDN cache і каталог, з якого Nginx віддає static assets. Це не те саме, що «старий код на сервері».

СимптомПерший крок перевіркиДе найчастіше проблема
502 Bad Gatewaycurl localhost:PORTЗастосунок або upstream
504 Gateway TimeoutЛоги та час відповіді backendЗавислий процес або timeout
403 ForbiddenNginx log і права файлівPermissions або location
404 тільки для APIПеревірити proxy/locationМаршрут reverse proxy
Сайт працює, API — ніDevTools NetworkAPI URL, CORS або proxy
Працює лише до виходу з SSHПеревірити менеджер процесуsystemd, PM2 або restart policy
Після reboot сайт не стартуєПеревірити enable/autostartАвтозапуск служби
Після deploy видно старий frontendПеревірити build і cacheCDN, browser cache або старі assets

Практичний чекліст перед відкриттям production

  • runtime має потрібну версію, а залежності встановлені з lock-файлу;
  • production-змінні середовища задані, секретів у Git немає;
  • production build створено, міграції БД виконані;
  • застосунок запускається як служба, має restart і стартує після reboot;
  • застосунок слухає очікуваний внутрішній порт;
  • Nginx проходить nginx -t, домен і HTTPS працюють коректно;
  • firewall не відкриває зайвих портів, а права на uploads, cache і logs коректні;
  • перевірено системні журнали, Nginx і журнал застосунку;
  • протестовано хоча б одну ключову функцію;
  • перед критичним оновленням є актуальна резервна копія.

Коли ручний deploy уже повторюється без сюрпризів, його можна автоматизувати через CI/CD. Але pipeline має повторювати зрозумілий і перевірений процес; інакше автоматизація лише швидше відтворюватиме ті самі помилки.

Production готовий не тоді, коли команда завершилася без червоного тексту, а коли застосунок переживає закриття SSH, стартує як служба, відповідає через HTTPS, працює з реальною БД і проходить короткий smoke-test. Саме це відділяє «код уже на сервері» від завершеного deploy.

Оновлено 12.08.2026

ChatGPT Perplexity Google (AI)