Как сделать постоянные URL-адреса для локальных Localhost-проектов
Создание сайта

Как сделать постоянные URL-адреса для локальных Localhost-проектов

Чтобы локальный адрес не менялся вместе с портом, закрепите имя проекта и поставьте перед dev-сервером обратный прокси-сервер (reverse proxy) на порты 80 и 443.

Елена Кравцова
Елена Кравцова
Редактор и автор статей19 мин

Чтобы локальный адрес не менялся вместе с портом, закрепите имя проекта и поставьте перед dev-сервером обратный прокси-сервер (reverse proxy) на порты 80 и 443. Для одного компьютера удобно имя вида app.localhost: оно ведёт на loopback, петлевой адрес самого устройства, без правки DNS. Для телефона и других устройств в локальной сети (LAN) нужен другой контур — стабильный LAN-IP компьютера, локальный DNS, например app.devbox.test, reverse proxy и правило firewall. Временный tunnel создаёт уже публичную ссылку и не заменяет локальный hostname.

Что именно должно оставаться постоянным

URL https://app.devbox.test/account складывается из схемы, имени, порта и пути. Если порт не указан, браузер использует 443 для HTTPS или 80 для HTTP. Файл hosts и DNS способны связать имя с IP-адресом, но не с :5173 или :3000. Порт скрывает reverse proxy: он принимает запрос на 80/443, читает имя хоста и передаёт трафик нужному приложению на его внутренний порт.

Задача Практичный адрес Что требуется Главная граница
Один проект на одном ПК, порт допустим http://shop.localhost:5173 Dev-сервер с закреплённым портом С телефона адрес откроет loopback телефона
Несколько проектов на одном ПК, без портов https://shop.localhost, https://api.localhost Caddy, nginx или Traefik на 80/443 Proxy должен быть запущен
Реалистичные поддомены и HTTPS https://app.devbox.test hosts или локальный DNS, reverse proxy, локальный сертификат Корневому центру сертификации должен доверять каждый клиент
Телефон, планшет, второй компьютер в LAN https://app.devbox.test Стабильный LAN-IP, внутренний DNS, proxy, firewall .localhost для этого не подходит
Несколько контейнеров Тот же внешний URL, внутри app:5173 Общая Docker-сеть и proxy IP контейнера нельзя считать постоянным
OAuth, webhook или показ человеку из другой сети https://preview.example.com Защищённый named tunnel или собственный домен Это публичная точка входа, а не локальное имя

Практичная лестница решений выглядит так: сначала *.localhost, затем локальный reverse proxy, при необходимости .test и локальный HTTPS, после этого LAN DNS. Публичный tunnel добавляют только для задачи, которой действительно нужен вход из интернета.

Как выбрать имя без будущего конфликта

Локальное имя нужно выбирать по области действия. Похожая запись в адресной строке ещё не означает одинаковое разрешение имён.

Суффикс Где уместен Как разрешается Чего не делать
.localhost Один компьютер Любое имя внутри зоны должно вести на loopback IPv4/IPv6 Не использовать для доступа к другому компьютеру
.test Тестовая зона в hosts или локальном DNS Как обычное DNS-имя, но зарезервировано для тестов Не ожидать, что оно заработает без записи
Поддомен принадлежащего вам домена Команда, split DNS, постоянный tunnel По вашим публичным или внутренним DNS-правилам Не смешивать публичный и внутренний ответ без плана
.local Обнаружение устройств через mDNS/Bonjour Multicast в текущем сетевом сегменте Не строить на нём обычную wildcard DNS-зону команды
Придуманный .dev, .app или другой TLD Не рекомендуется Может оказаться реальным публичным доменом Не полагаться на то, что имя «никто не зарегистрирует»

Специальное поведение .localhost и тестовое назначение .test закреплены в RFC 6761. Это важнее привычки использовать project.local: .local обслуживает Multicast DNS, может конфликтовать с Bonjour, по-разному работать между VLAN и получать числовой суффикс при совпадении имён.

Для одного ПК начните с shop.localhost. Для общей сети выберите shop.devbox.test либо поддомен реального домена, которым владеет команда. Не подменяйте локальную настройку публичной A-записью на 127.0.0.1 или частный адрес 192.168.x.x: это смешивает два контура и может попасть под защиту от DNS rebinding.

Самый простой рецепт на одном компьютере

Если порт в адресе не мешает, файл hosts не нужен. Закрепите порт dev-сервера и обращайтесь к проекту через поддомен .localhost:

http://shop.localhost:5173

Для Vite порт лучше сделать строгим. Иначе при занятом 5173 сервер без ошибки перейдёт на 5174, и URL снова изменится:

npm run dev -- --host 127.0.0.1 --port 5173 --strictPort

Проверьте имя и порт в PowerShell:

[System.Net.Dns]::GetHostAddresses('shop.localhost')
Test-NetConnection shop.localhost -Port 5173
curl.exe -I http://shop.localhost:5173

Сервер, доступный только через 127.0.0.1, не принимает соединения с LAN. Это безопасная настройка по умолчанию. Если приложение должно открываться с телефона, не меняйте bind на 0.0.0.0 автоматически — сначала настройте LAN-сценарий и ограничьте firewall.

Когда всё-таки нужен файл hosts

hosts пригодится для точного имени в зоне .test, например app.devbox.test. На Windows файл находится в %SystemRoot%\System32\drivers\etc\hosts, а редактору нужны права администратора.

Сначала создайте резервную копию в пользовательской временной папке и откройте системный файл:

$hostsPath = Join-Path $env:SystemRoot 'System32\drivers\etc\hosts'
$backupPath = Join-Path $env:TEMP ("hosts-{0}.bak" -f (Get-Date -Format 'yyyyMMdd-HHmmss'))
Copy-Item -LiteralPath $hostsPath -Destination $backupPath
Start-Process notepad.exe -ArgumentList $hostsPath -Verb RunAs

Добавьте только нужные имена. Несколько алиасов можно указать в одной строке:

127.0.0.1 app.devbox.test api.devbox.test

После сохранения очистите системный кэш и проверьте результат:

Clear-DnsClientCache
[System.Net.Dns]::GetHostAddresses('app.devbox.test')
curl.exe -I http://app.devbox.test:5173

Эта запись не убирает :5173. Она также действует только на данном компьютере и не поддерживает *.devbox.test. Для маски всех поддоменов (wildcard) нужен локальный DNS, а для адреса без порта — reverse proxy.

При откате удалите ровно добавленную строку и снова выполните Clear-DnsClientCache. Не копируйте старый backup поверх текущего файла, пока не сравните версии: после резервного копирования другой инструмент или пользователь мог добавить свои записи.

Пути и очистка кэша в macOS и Linux

Система Файл Типичная очистка кэша Оговорка
Windows 10/11 %SystemRoot%\System32\drivers\etc\hosts Clear-DnsClientCache Браузерный Secure DNS может иметь отдельный кэш
macOS /etc/hosts sudo dscacheutil -flushcache, затем sudo killall -HUP mDNSResponder Не используйте .local как обычную DNS-зону
Linux с systemd-resolved /etc/hosts sudo resolvectl flush-caches Resolver и команда зависят от дистрибутива

В macOS и Linux сначала копируют /etc/hosts в отдельный backup, затем редактируют через sudo. Принцип отката тот же: удалить собственную строку, не затирая чужие изменения.

Как убрать порты с помощью Caddy

Reverse proxy слушает стандартные 80/443 и направляет запрос по hostname. Для локальной разработки Caddy удобен тем, что конфигурация короткая, WebSocket проксируется без отдельной секции, а HTTPS можно выпустить через встроенный локальный центр сертификации.

Proxy Когда выбирать Сильная сторона Что потребует внимания
Caddy Один разработчик, небольшой набор проектов Короткий файл и локальный HTTPS Доверие к локальному CA и постоянное хранилище /data
nginx Команда уже использует nginx в production Полный явный контроль Сертификаты, proxy headers и WebSocket настраиваются вручную
Traefik Много динамических Docker-сервисов Маршруты через labels и автоматическое обнаружение Защита Docker socket и exposedByDefault=false

Создайте рядом с проектом файл Caddyfile:

app.localhost {
    tls internal
    reverse_proxy 127.0.0.1:5173
}

api.localhost {
    tls internal
    reverse_proxy 127.0.0.1:8080
}

До запуска убедитесь, что нужные порты свободны:

Get-NetTCPConnection -State Listen |
    Where-Object LocalPort -In 80, 443, 5173, 8080 |
    Select-Object LocalAddress, LocalPort, OwningProcess

Проверьте конфигурацию, затем запустите proxy:

caddy validate --config .\Caddyfile
caddy run --config .\Caddyfile

После этого внешние адреса не содержат порт:

https://app.localhost/
https://api.localhost/

Caddy попробует установить собственный корневой сертификат в trust store. Если процессу не хватило прав, браузер покажет ошибку доверия. На личной машине разработчика можно проверить созданный CA и выполнить caddy trust из повышенной консоли. Не устанавливайте неизвестный корневой сертификат и не передавайте его private key.

Проверка без -k важна: curl -k отключает валидацию TLS и доказывает только доступность сервера.

curl.exe -I https://app.localhost/

Если приложение строит ссылки как http://127.0.0.1:5173, настройте доверие к proxy в самом framework и внешний APP_URL. Caddy передаёт стандартные X-Forwarded-* заголовки, но приложение должно обрабатывать их только от доверенного proxy.

Кроссплатформенный вариант Docker Compose и Caddy

Следующий пример рассчитан на Vite-приложение, которое собирается из текущей папки. Для другого стека замените build, command и внутренний порт, сохранив принцип: приложение не публикуется на хост, а доступно только proxy в общей сети.

name: localdev-shop

services:
  app:
    build: .
    command: ["npm", "run", "dev", "--", "--host", "0.0.0.0", "--port", "5173", "--strictPort"]
    expose:
      - "5173"
    networks:
      - devnet

  proxy:
    image: caddy:2-alpine
    depends_on:
      - app
    ports:
      - "127.0.0.1:80:80"
      - "127.0.0.1:443:443"
      - "[::1]:80:80"
      - "[::1]:443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config
    networks:
      - devnet

networks:
  devnet:

volumes:
  caddy_data:
  caddy_config:

В контейнерном Caddyfile upstream задаётся именем сервиса, а не localhost и не IP контейнера:

app.localhost {
    tls internal
    reverse_proxy app:5173
}

Запуск и базовая проверка:

docker compose config
docker compose up -d --build
docker compose ps
docker compose logs proxy --tail 100
curl.exe -kI https://app.localhost/

Последняя команда использует -k только для первичной диагностики маршрута. Для полноценной проверки нужно доверить корень Caddy на хосте и повторить запрос без -k. Контейнер не может автоматически установить CA в trust store Windows, macOS или Linux. Скопируйте публичный корневой сертификат после первого запуска:

docker compose cp proxy:/data/caddy/pki/authorities/local/root.crt .\.local-caddy-root.crt

На Windows импортируйте его в хранилище доверия текущего пользователя и сохраните точный цифровой отпечаток (thumbprint) для отката:

$importedCert = Import-Certificate `
    -FilePath .\.local-caddy-root.crt `
    -CertStoreLocation Cert:\CurrentUser\Root
$importedCert.Thumbprint | Set-Content .\.local-caddy-root.thumbprint
curl.exe -I https://app.localhost/

На macOS сертификат добавляют в Keychain Access или командой security add-trusted-cert; на Debian/Ubuntu — копируют с расширением .crt в /usr/local/share/ca-certificates/ и запускают update-ca-certificates. Firefox, Node.js, Java, контейнеры и мобильные устройства могут использовать отдельные trust stores. Проверяйте каждый реальный клиент, а не только Chrome на компьютере.

Named volume caddy_data сохраняет локальный CA между перезапусками. Обычный docker compose down не удаляет его. Команда docker compose down -v создаст при следующем запуске новый CA и сломает прежнее доверие, поэтому -v нельзя добавлять в автоматическую очистку.

Где нужен mkcert

mkcert удобен, если TLS завершает nginx, встроенный Node-сервер или другой инструмент без собственного локального центра сертификации (CA). Утилита создаёт локальный CA, устанавливает его корень в поддерживаемые хранилища доверия и выпускает сертификаты с нужными альтернативными именами (Subject Alternative Names, SAN).

mkcert -install
mkcert `
    -cert-file .\certs\devbox.test.pem `
    -key-file .\certs\devbox.test-key.pem `
    "devbox.test" "*.devbox.test"

Wildcard *.devbox.test не покрывает само имя devbox.test, поэтому в команде указаны оба имени. Он также не покрывает два уровня вроде x.api.devbox.test. Сертификат и private key затем явно подключают в Caddy/nginx/приложении: mkcert не меняет конфигурацию сервера.

Добавьте certs/*.pem, локальные CA-файлы и thumbprint-файлы в .gitignore. Особо защищайте файл rootCA-key.pem в каталоге, который показывает mkcert -CAROOT: тот, кто получит его, сможет выпускать сертификаты, доверенные вашей машиной. Для команды безопаснее версионировать Compose/Caddyfile и давать каждому разработчику создать собственный CA, чем раздавать один private key.

Постоянный адрес для телефона и других устройств в LAN

app.localhost на телефоне означает «этот телефон». Чтобы открыть приложение на ноутбуке, нужны пять согласованных частей:

  1. Закрепите за dev-компьютером LAN-IP через DHCP reservation на роутере, например 192.168.1.50. Ручной статический IP вне DHCP-пула тоже возможен, но им сложнее управлять.
  2. Создайте внутреннее DNS-правило app.devbox.test → 192.168.1.50 или wildcard *.devbox.test → 192.168.1.50.
  3. Привяжите proxy к LAN-IP на 80/443, а само приложение оставьте доступным только proxy.
  4. Разрешите вход на 80/443 только из нужной private subnet в firewall.
  5. Установите доверенный корень локального CA на каждое тестовое устройство либо используйте домен и сертификат, которым эти устройства уже доверяют.

Для dnsmasq wildcard-правило выглядит так:

address=/devbox.test/192.168.1.50

В AdGuard Home можно создать DNS rewrite *.devbox.test на тот же IP. В Pi-hole точные Local DNS records создаются через интерфейс; wildcard обычно добавляют штатным способом конфигурации используемого dnsmasq/FTL, а не десятками строк на клиентах. Не открывайте DNS-сервис в интернет и ограничьте интерфейсы локальной сетью.

Клиенты должны действительно использовать этот resolver. Private DNS, DNS-over-HTTPS в браузере, VPN или корпоративная политика способны обойти настройку роутера. Проверяйте конкретный DNS-сервер:

nslookup app.devbox.test 192.168.1.1
Test-NetConnection 192.168.1.50 -Port 443
curl.exe -I https://app.devbox.test/

Адрес DNS-сервера в примере замените на фактический. Если DNS отвечает правильно, а соединения нет, проверьте bind proxy и firewall. Если всё работает на обычном Wi‑Fi, но не на гостевом, вероятна client isolation. mDNS между VLAN также не проходит без отражателя, но для обычной DNS-зоны он и не должен быть нужен.

Для Compose замените loopback-привязки proxy на точный LAN-IP:

ports:
  - "192.168.1.50:80:80"
  - "192.168.1.50:443:443"

Не используйте 80:80 без адреса, если доступ со всех интерфейсов не был намеренным: Docker обычно публикует такой порт на 0.0.0.0 и [::]. Подробная модель описана в документации Docker о публикации портов.

Как настроить dev-сервер за proxy

Proxy не отменяет защиту framework. Приложение должно принимать только ожидаемые hostname/origin и слушать ровно тот интерфейс, с которого к нему приходит proxy.

Vite

При нативном Caddy и нативном Vite оставьте bind на 127.0.0.1. В Compose используйте 0.0.0.0, но не публикуйте порт приложения на хост. Разрешайте точные имена:

import { defineConfig } from 'vite'

export default defineConfig({
  server: {
    host: '0.0.0.0',
    port: 5173,
    strictPort: true,
    allowedHosts: ['app.localhost', 'app.devbox.test'],
  },
})

Не ставьте allowedHosts: true и cors: true как универсальное исправление: это открывает dev-сервер для DNS-rebinding запросов и может раскрыть исходники. Если Hot Module Replacement не подключается через HTTPS-proxy, сначала проверьте WebSocket в DevTools. Только затем задавайте внешний wss, hostname и clientPort: 443 в server.hmr.

Next.js и другие frameworks

Next.js запускается на нужном внутреннем интерфейсе и порту:

npx next dev -H 0.0.0.0 -p 3000

Дополнительные dev-origin перечисляются в next.config.js:

module.exports = {
  allowedDevOrigins: ['app.devbox.test', '*.devbox.test'],
}

Сходные проверки есть у других стеков: Django ALLOWED_HOSTS и CSRF trusted origins, Rails config.hosts, Laravel APP_URL и trusted proxies. Добавляйте конкретные имена, а proxy headers принимайте только от своего proxy. Иначе злоумышленник в LAN или через DNS rebinding сможет подставить Host, а приложение построит опасную ссылку сброса пароля или OAuth callback.

Docker: три разных значения слова localhost

В контейнерной схеме легко перепутать три адресных пространства:

  • localhost в браузере — компьютер, на котором открыт браузер;
  • localhost внутри контейнера proxy — сам контейнер proxy;
  • app:5173 в Compose-сети — сервис app и его container port.

Контейнеры получают новые IP после пересоздания, но Compose сохраняет service name. Поэтому reverse_proxy 172.20.0.4:5173 хрупок, а reverse_proxy app:5173 воспроизводим. Host port вроде 5173 вообще не нужен для связи proxy → app.

Если контейнеру всё же нужен сервис, запущенный на хосте, Docker Desktop предоставляет host.docker.internal. На Linux поведение зависит от установки; может потребоваться явный host-gateway. Для командного проекта надёжнее поместить proxy и приложение в общую Compose-сеть, чем строить основную схему на платформенном исключении.

Cookies, service workers и один origin

Смена адреса меняет поведение браузера сильнее, чем кажется:

  • scheme, hostname и port образуют origin, поэтому http://app.localhost:3000 и http://app.localhost:5173 — разные origins;
  • cookie не изолируются портом; атрибут Domain не содержит порт;
  • cookie без Domain остаётся host-only и не передаётся соседнему поддомену;
  • SameSite=None требует Secure, а production-like проверку лучше вести через доверенный HTTPS;
  • __Host- cookie требует HTTPS, Path=/ и отсутствие Domain;
  • service worker привязан к origin и scope. Старый worker может продолжить обслуживать старый hostname/порт после миграции.

Для разработки frontend и API проще использовать один внешний origin: https://app.devbox.test/ и путь /api, который proxy отправляет бэкенду. Это уменьшает объём CORS и cookie-настроек. Если production использует app.example.com и api.example.com, тестируйте раздельные поддомены намеренно: задайте CORS allowlist, CSRF origins, credentials, SameSite и cookie scope.

После смены URL откройте DevTools → Application, проверьте Cookies и Service Workers, отмените ненужные регистрации и очистите данные старого origin. В консоли полезны две проверки:

window.location.origin
window.isSecureContext

HTTP на localhost, loopback и обычно поддоменах .localhost браузеры считают потенциально доверенным локальным контекстом. Произвольное имя .test и LAN-IP по HTTP такого исключения не получают, поэтому для телефона, WebAuthn, PWA и честной проверки Secure-cookie используйте HTTPS.

OAuth redirect URI без сюрпризов

Для web OAuth зарегистрируйте полный callback, например:

https://app.devbox.test/auth/google/callback

Провайдер обычно сравнивает scheme, hostname, port, path и завершающий / буквально. http://localhost:3000/callback, http://127.0.0.1:3000/callback и https://app.localhost/callback — три разных URI. Некоторые провайдеры разрешают HTTP только для localhost/loopback и отклоняют .test, даже если сертификат доверен локально. Тогда используйте зарегистрированное loopback-исключение или постоянный named tunnel на принадлежащем вам домене.

Для desktop/native приложения правило иное: рекомендуемый callback слушает случайный порт на 127.0.0.1 или [::1], открывается только на время авторизации и использует Proof Key for Code Exchange (PKCE). Разрешение менять порт для loopback native callback нельзя переносить на обычное web-приложение.

Не стройте callback из непроверенного входного Host. Задайте внешний base URL в конфигурации приложения и доверьте forwarded headers только своему proxy.

Когда нужен tunnel

Tunnel нужен, если запрос приходит не из вашей LAN: внешний OAuth-провайдер требует публичный callback, платёжный сервис отправляет webhook, заказчик открывает демо из другой сети. Пример временного запуска:

cloudflared tunnel --url http://127.0.0.1:5173

Quick Tunnel выдаёт случайный *.trycloudflare.com и меняет его при новом запуске. Такой URL не постоянен, не имеет гарантии доступности и не подходит для долгоживущего OAuth redirect. Стабильный адрес даёт named tunnel с закреплённым hostname либо собственная публичная среда preview.

Tunnel превращает dev-сервер в публичный origin, даже если входящий порт роутера закрыт. Перед запуском:

  • включите Access/OAuth/basic auth, если сервис не должен быть доступен всем;
  • используйте тестовые данные и отдельные секреты;
  • отключите debug toolbar, admin-панель, directory listing и опасные dev-endpoints;
  • оставьте exact allowed hosts/origins;
  • проверяйте подпись webhook и защищайтесь от повторной доставки;
  • остановите процесс сразу после теста.

Не отправляйте tunnel token в репозиторий или лог. Быстрое шифрованное соединение до провайдера tunnel не заменяет авторизацию вашего приложения.

Проверка по слоям

Не начинайте отладку с браузера. Проверяйте каждый слой отдельно — так понятнее, где именно сломалась цепочка.

Слой Windows/кроссплатформенная проверка Успешный признак
Имя [System.Net.Dns]::GetHostAddresses('app.devbox.test') или nslookup для DNS Нужный loopback/LAN-IP, без неожиданного публичного адреса
Listen socket Get-NetTCPConnection -State Listen Proxy на 80/443, приложение на ожидаемом внутреннем порту
Compose docker compose ps Оба сервиса running, нет случайного host port у app
Маршрут proxy curl.exe -kI https://app.devbox.test/ Не 502; -k только на этом диагностическом шаге
TLS trust curl.exe -I https://app.devbox.test/ Нет ошибки неизвестного CA или несовпадения имени
DNS в обход resolver curl.exe --resolve app.devbox.test:443:127.0.0.1 -I https://app.devbox.test/ Proxy и сертификат работают независимо от DNS
Browser origin window.location.origin Точно ожидаемая схема, hostname и порт
Secure context window.isSecureContext true там, где нужны защищённые Web API
Удалённое устройство DNS lookup, затем HTTPS-запрос с него Разрешение, сеть и trust работают именно на клиенте

Проверяйте IPv4 и IPv6 отдельно. Имя может вернуть ::1, пока сервер слушает только 127.0.0.1, или наоборот. Временно отключать IPv6 ради маскировки ошибки не стоит: лучше согласовать записи и listen sockets.

Типовые неисправности

Симптом Вероятная причина Что проверить
Имя не разрешается Опечатка, DNS-кэш, Secure DNS/VPN, клиент использует другой resolver hosts, A/AAAA, nslookup <name> <dns-ip>, браузерный DNS
Connection refused На IP/порту никто не слушает Процесс, bind 127.0.0.1 против LAN-IP, конфликт 80/443
502 Bad Gateway Proxy обращается не туда В Docker использовать app:5173, не localhost:5173 и не IP контейнера
Браузер сообщает о недоверенном сертификате Root CA не установлен на этом клиенте или Caddy потерял volume Issuer, Subject Alternative Names, fingerprint root, наличие caddy_data
Сертификат доверен, но имя не совпадает Сертификат не содержит hostname Выпустить сертификат с точными SAN; wildcard не покрывает корень и два уровня
403 или сообщение blocked host/origin Защита dev-сервера Точный allowedHosts, allowedDevOrigins, CSRF/CORS allowlist
Redirect loop или ссылка содержит внутренний порт Неверный внешний base URL/proxy trust X-Forwarded-Proto, Host, trusted proxies, APP_URL
Страница есть, HMR не подключается WebSocket идёт на внутренний порт/неверную схему Network → WS, внешний wss, host и client port 443
На ноутбуке работает, на телефоне нет .localhost, loopback bind, firewall, guest isolation или нет LAN DNS LAN-IP, resolver телефона, bind proxy, private network profile
После смены URL показывается старое приложение Service worker и Cache Storage старого origin DevTools Application, unregister, Clear site data
OAuth redirect_uri_mismatch Отличается хотя бы один компонент URI Scheme, hostname, port, path, регистр, trailing slash в панели провайдера

Как сделать настройку воспроизводимой для команды

Постоянный URL полезен только тогда, когда новый разработчик получает его без ручного квеста. В репозиторий стоит положить:

  • compose.yaml и Caddyfile;
  • .env.example с внешними URL без секретов;
  • скрипт read-only проверки портов, DNS и HTTPS;
  • короткую таблицу «имя → сервис → container port»;
  • инструкции для Windows, macOS и Linux;
  • точный rollback.

Не коммитьте hosts-файл целиком, private keys локального CA, tunnel credentials, OAuth client secrets и сертификаты с ключами. Для LAN документируйте владельца DNS-правила и DHCP reservation. Для каждого проекта назначьте уникальные имена и внутренние порты, даже если наружу все выходят через 443.

Очистка и откат

Откатывайте изменения в обратном порядке и только по точным идентификаторам:

  1. Остановите Quick/named tunnel и отзовите больше не нужный token.
  2. Удалите тестовый OAuth redirect из панели провайдера, если он больше не используется.
  3. Остановите proxy. Для Compose выполните docker compose down; не добавляйте -v, пока CA нужен другим проектам.
  4. Удалите только собственные hosts-строки и очистите DNS-кэш.
  5. Удалите точное wildcard/Local DNS rule и проверьте, что обычные запросы снова дают ожидаемый ответ.
  6. Удалите созданное firewall rule по его имени, не сбрасывая firewall целиком.
  7. Удалите локальный root CA только если ни один проект ему больше не доверяет. На Windows используйте сохранённый thumbprint:
$thumbprint = Get-Content .\.local-caddy-root.thumbprint
Remove-Item -LiteralPath "Cert:\CurrentUser\Root\$thumbprint"
  1. Удалите точные сертификаты и private keys проекта. Для mkcert команда mkcert -uninstall убирает его CA из поддерживаемых trust stores текущей машины, но корень, вручную установленный на телефоне или другом компьютере, нужно удалить там отдельно.
  2. Очистите cookie, Cache Storage и service workers старого origin.

Перед удалением CA сравните fingerprint и убедитесь, что он не общий для других локальных адресов. Простое удаление папки Caddy при оставшемся доверенном корне оставляет ненужное доверие, а удаление volume при оставшемся проекте вызывает неожиданную ротацию CA.

Вывод

Для одного компьютера оптимальная основа — *.localhost: имя зарезервировано, не требует hosts и не уходит в публичный DNS. Reverse proxy на 80/443 превращает app.localhost:5173 в постоянный https://app.localhost и раздаёт разные имена нескольким проектам. Для LAN используйте .test или поддомен собственного домена, стабильный IP dev-хоста, внутренний DNS, точную привязку proxy, firewall и доверенный HTTPS на каждом клиенте.

В Docker маршрутизируйте по service name, не по IP и не через localhost. В framework оставляйте точные allowed hosts/origins. OAuth, cookies и service workers проверяйте на окончательном origin. Tunnel считайте отдельной публичной публикацией: Quick URL временный, а постоянный внешний callback требует named tunnel или собственной preview-среды и обязательной защиты доступа. Читать полный обзор сервиса Docker

Автор статьи

Елена Кравцова — Редактор и автор статей
Елена Кравцова

Редактор и автор статей

Пишет экспертные материалы о цифровом маркетинге и автоматизации. Журналист с опытом в деловых медиа, отвечает за качество и достоверность публикаций.

Вопросы и ответы

Нет. hosts сопоставляет hostname с IP-адресом. Используйте порт в URL либо поставьте reverse proxy на 80/443, который направит имя на нужный внутренний порт.

Каждое устройство разрешает .localhost в собственный loopback. На телефоне это адрес телефона. Для доступа к ноутбуку нужен его LAN-IP, внутренний DNS и proxy, доступный из сети.

Для одного устройства — .localhost. Для управляемой тестовой DNS-зоны — .test. .local оставьте mDNS/Bonjour: обычная DNS-зона с таким суффиксом конфликтует с механизмами обнаружения устройств.

Нет. Перечисляйте точные имена либо настройте wildcard во внутреннем DNS, например dnsmasq или AdGuard Home. Поддомены .localhost обычно не требуют wildcard-записи, потому что вся зона указывает на loopback.

Не стоит. .dev — реальная публичная зона с принудительным HTTPS в браузерах. Используйте зарезервированный .test, .localhost или поддомен домена, которым вы владеете.

Не всегда: браузеры считают loopback потенциально доверенным для многих защищённых API. Но HTTPS нужен для production-like cookies, произвольного .test, LAN-устройств, части OAuth/WebAuthn-сценариев и проверки реального TLS-контура.

Trust store локален для устройства, а иногда и для приложения. Установите публичный корень своего local CA на телефон и включите доверие по правилам его ОС либо используйте публично доверенный сертификат на принадлежащем вам домене. Private key CA на телефон не копируют.

Внутри контейнера proxy localhost означает сам proxy-контейнер. Подключите оба сервиса к одной Compose-сети и направляйте запрос на app:5173, где app — имя сервиса.

Порт входит в origin, но не в область cookie. Два приложения на одном hostname и разных портах могут увидеть одни и те же подходящие cookies. Разводите проекты по hostname и используйте host-only cookies.

Нет. Быстрый tunnel выдаёт случайный hostname и меняет его при новом запуске. Для постоянного callback нужен named tunnel с закреплённым hostname или отдельная preview-среда.

Зависит от провайдера. Многие требуют публичный домен и точное совпадение URI, разрешая HTTP только для localhost/loopback. Сначала проверьте правила провайдера, затем зарегистрируйте точную схему, hostname, порт и путь.

Храните в Git Compose/Caddy-конфигурацию, список имён и скрипты проверки. Каждый разработчик создаёт и доверяет свой local CA. Общий CA допустим только при отдельном защищённом управлении ключом, ротации и процедуре отзыва.

Смотрите также

Поделиться

Комментарии(0)

Оставьте комментарий

Войдите, чтобы присоединиться к обсуждению