В свободное время я потихоньку развиваю свою лабораторию. Мне нравятся маломощные и компактные устройства, но проблема заключается в том, что не все рабочие инструменты доступны на таком железе.
Я решил погрузиться в вопрос и начал изучать решения, но концепция удаленного доступа к ПК мне не понравилась. Разные операционные системы и платформы сильно ограничивают в выборе привычных инструментов, а быстро подправить код бывает нужно здесь и сейчас. Поэтому я решил развернуть собственный code-server на персональном домене и с готовым SSL-сертификатом.
Структура проекта
Хотелось бы запускать все «из коробки», поэтому будем использовать docker-compose. Так мы сможем сразу скачивать и обновлять последние версии, удобно просматривать логи, а также в случае чего изолировать среду сервера от среды пользователя с помощью Docker.
Для получения сертификата нам понадобятся Certbot и Nginx. Однако при запуске без предварительной настройки Nginx сразу не стартует: он пытается достучаться до страницы уже с сертификатом. Поэтому первый запуск выполняется с тестовым конфигом nginx.bootstrap.conf.
По итогу в проекте мы имеем следующие файлы:
docker-compose.yml— полный конфиг Docker,nginx.conf— постоянный конфиг Nginx,nginx.bootstrap.conf— временный конфиг Nginx.
Подготовка
Теперь определим, что нам нужно для настройки своего домена: сам домен, сервер и его IP-адрес. В качестве сервера выступает простой облачный сервер, вместе с которым мы и получаем IP-адрес. Сам же домен необходимо связать с этим IP через DNS-сервер. Эта процедура может занимать от нескольких минут до нескольких часов, так как делегирование адреса происходит не мгновенно.
Если записи еще не успели обновиться, Certbot завершится ошибкой NXDOMAIN или Connection refused. Поэтому, прежде чем запускать Certbot, убедитесь, что домен уже резолвится в IP-адрес вашего сервера. Проверить распространение DNS-записей можно как командой dig @a.ns.selectel.ru ВАШ.ДОМЕН.РФ A.
После исполнения команды найдите строчку:
;; ANSWER SECTION:
ВАШ.ДОМЕН.РФ 3539 IN A IP-адрес сервера
Если IP совпадает с IP адресом вашего сервера, то можно смело переходить дальше.
Разберем вариант, когда мы хотим выделить code-server на поддомен. Заходим в панель управления Selectel и выбираем проект (так как у меня их несколько, я выбираю тестовый).

Нажимаем Создать ресурс и переходим в раздел Домены.

Далее регистрируем интересующий нас домен. Более подробно ознакомиться с правилами использования и доступными доменами можно в документации. После регистрации домена возвращаемся на главную страницу, снова нажимаем Добавить ресурс и выбираем Доменная зона.

Доменная зона позволяет связать IP-адрес и доменное имя, данные о которых впоследствии поступят на DNS-серверы Selectel, а затем и на другие DNS-серверы. К доменной зоне мы еще вернемся чуть позже, а пока что необходимо арендовать сервер.
Под наши задачи подойдет любой сервер, который поддерживает виртуализацию и имеет минимум 2 vCPU и 2 ГБ оперативной памяти. Мне удобнее работать в новой зоне ru-6a, так как задержки там для меня минимальны. Если вы планируете выполнять код на этом же сервере, советую выделить больше ресурсов, но мне хватит и этих мощностей для простой работы с кодом и хранения данных.
Переходим на главную страницу и выбираем пункт Облачный сервер.

Затем задаем конфигурацию сервера.

В итоге у меня получились следующие настройки:
- непрерываемый сервер на базе образа Ubuntu 26.04 LTS,
- конфигурация линейки Standard (SL2.2-4096-AMD),
- 2 vCPU на выделенных ядрах с частотой 2.2–2.4 ГГц,
- 4 ГБ оперативной памяти,
- базовый сетевой диск HDD объемом 20 ГБ.
После успешного создания инстанса панель сразу отображает активный статус виртуальной машины, ее внутренний и публичный IP-адреса, а также готовую команду для первичного подключения по SSH.

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

Установка Docker
Делегирование доменной зоны может занимать несколько часов, поэтому перейдем к настройке и установке нашего code-server.
Подключаемся к серверу по SSH и устанавливаем Docker. Для этого сначала настраиваем apt-репозиторий:
sudo apt update
sudo apt install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
# Данный блок ввести целиком
sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF
sudo apt update
Затем устанавливаем пакеты Docker. Для установки последней версии выполните следующую команду:
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
После установки стоит убедиться, что Docker запущен:
sudo systemctl status docker
Если Docker не запущен, его нужно запустить вручную:
sudo systemctl start docker
Конфигурационные файлы
После первичной настройки создаем или переносим конфигурационные файлы. Под проект лучше выделить отдельную папку и настроить права доступа.
Начнем разбор с файла docker-compose.yml. Это основной конфигурационный файл: в нем описано, контейнеры каких версий, в каком количестве и как именно будут запущены.
services:
nginx:
image: nginx:latest
container_name: nginx-proxy
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./certbot/conf:/etc/letsencrypt:ro
- ./certbot/www:/var/www/certbot:ro
command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'"
restart: unless-stopped
depends_on:
- code-server
certbot:
image: certbot/certbot:latest
container_name: certbot-agent
volumes:
- ./certbot/conf:/etc/letsencrypt
- ./certbot/www:/var/www/certbot
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done'"
restart: unless-stopped
code-server:
image: lscr.io/linuxserver/code-server:latest
container_name: code-server
environment:
- PUID=1000
- PGID=1000
- TZ=Etc/UTC
- PASSWORD= ВАШ_ПАРОЛЬ
- SUDO_PASSWORD= ВАШ_ПАРОЛЬ
- PROXY_DOMAIN= ВАШ.ДОМЕН.РФ
volumes:
- ./code-server-config:/config
expose:
- "8443"
restart: unless-stopped
Блоки nginx и certbot не сильно отличаются от аналогичных конфигов в других проектах: они точно так же перенаправляют конфигурационные файлы и не могут работать без запущенного code-server. Подобную настройку можно встретить во многих руководствах. А вот блок самого code-server уже заставляет вникать в параметры запуска. Всего доступно 10 опций, которые можно применить при старте:
PUID=1000— идентификатор пользователя;PGID=1000— идентификатор группы;TZ=Etc/UTC— временная зона;PASSWORD=password(опционально) — пароль для входа;HASHED_PASSWORD=(опционально) — хеш пароля для входа;SUDO_PASSWORD=password(опционально) — пароль суперпользователя;SUDO_PASSWORD_HASH=(опционально) — хеш пароля суперпользователя;PROXY_DOMAIN=code-server.my.domain(опционально) — проксирование на ваш домен;DEFAULT_WORKSPACE=/config/workspace(опционально) — папка, которую code-server открывает по умолчанию;PWA_APPNAME=code-server(опционально) — изменение заголовка (title) во вкладке браузера (по умолчанию отображается в формате:рабочий_файл-рабочая_директория-code-server).
Подробнее о них можно почитать в официальной документации на Docker Hub.
Для наших задач понадобятся три параметра:
PASSWORD— отвечает за пароль при входе,SUDO_PASSWORD— пароль суперпользователя для исполнения команд на хосте,PROXY_DOMAIN— ваш домен.
Измените их под свои данные и придумайте пароль (НЕ из примера).
Далее укажите ваш домен в файле nginx.conf. В нем прописана переадресация с 80-го порта на 443-й и перенаправление на http://code-server:8443/.
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
server {
listen 80;
server_name ВАШ.ДОМЕН.РФ ;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://hostrequest_uri;
}
}
server {
listen 443 ssl;
http2 on;
server_name ВАШ.ДОМЕН.РФ ;
ssl_certificate /etc/letsencrypt/live/ВАШ.ДОМЕН.РФ/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ВАШ.ДОМЕН.РФ/privkey.pem;
location / {
proxy_pass http://code-server:8443/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400;
}
}
}
Остается последний файл. В нем (почти) ничего не придется менять, так как он нужен для первичной выдачи сертификата Let’s Encrypt. Если вы захотите проксировать другие ваши проекты на том же сервере, то менять следует файл nginx.conf.
nginx.bootstrap.conf
events { worker_connections 1024; }
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
server {
listen 80;
server_name ВАШ.ДОМЕН.РФ;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 200 "bootstrap ok\n";
}
}
}
Развертывание
Теперь приступаем к запуску проекта на сервере. Создаем директорию для проекта и переносим туда все нужные файлы. nginx.bootstrap.conf можно сохранить на случай будущего переноса сервиса.
В файле docker-compose.yml находим строку:
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
И меняем ее на:
volumes:
- ./nginx.bootstrap.conf:/etc/nginx/nginx.conf:ro
Запускаем проект с помощью Docker Compose:
docker compose up -d nginx
docker compose ps
docker compose logs nginx --tail=50
В итоге у нас должны успешно запуститься и code-server, и nginx, а при попытке входа на сайт — скачиваться файл, содержащий bootstrap ok.
Теперь можно выпустить SSL-сертификат для нашего сайта.
Проверяем выпуск сертификата:
docker compose run --rm --entrypoint certbot certbot certonly \
--webroot -w /var/www/certbot \
-d ВАШ.ДОМЕН.РФ \
--email ВАША.ЭЛЕКТРОННАЯ@ПОЧТА.РФ \
--agree-tos --no-eff-email \
--non-interactive \
--dry-run
Если вы увидели сообщение The dry run was successful., значит, можно выпустить полноценный сертификат. Выполняем ту же команду, но уже без флага --dry-run.
После успешного выпуска снова открываем docker-compose.yml, где находим:
volumes:
- ./nginx.bootstrap.conf:/etc/nginx/nginx.conf:ro
И меняем монтирование конфига Nginx:
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
Теперь перезапускаем Docker:
docker compose down
docker compose up -d
docker compose ps
Если после выполнения команды docker compose ps ни один контейнер не ушел в циклическую перезагрузку, значит, все сделано правильно. Заходим на сайт и проверяем результат.

Мы видим, что сайт все еще отображается без SSL-сертификата — это происходит из-за того, что кэш старой страницы сохранился без SSL. Однако если зайти в режиме инкогнито, с другого устройства или просто подождать и перезайти, то сертификат подтянется корректно.
Вам также могут пригодиться эти инструкции
Также может возникнуть ситуация, когда Certbot выпускает сертификат с префиксом -0001. В этом случае необходимо открыть файл nginx.conf и обновить пути к сертификатам.
Замените следующие строки с исходным путем:
ssl_certificate /etc/letsencrypt/live/ВАШ.ДОМЕН.РФ/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ВАШ.ДОМЕН.РФ/privkey.pem;
Укажите новый путь с префиксом:
ssl_certificate /etc/letsencrypt/live/ВАШ.ДОМЕН.РФ-0001/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ВАШ.ДОМЕН.РФ-0001/privkey.pem;
А затем перезапустите Nginx в контейнере следующей командой:
docker compose exec nginx nginx -s reload

В итоге мы получили рабочий сайт с SSL-сертификатом и развернутым на нем code-server. Если вам потребуется сменить пароль для входа, его можно изменить в конфигурационном файле по адресу code-server-config/.config/code-server/config.yaml.
Мы настроили среду разработки, доступную с любого устройства и из любой точки, где есть интернет. Безусловно, у системы есть свои нюансы — например, не самая удобная кастомизация при работе с планшетов и телефонов: изменение размеров окон может зависнуть на слайдере, а экранная клавиатура полностью перекрывает окно shell.

Однако возможность получить полноценный доступ без подключения по SSH, сторонних клиентов и долгой настройки лично для меня делает этот инструмент максимально комфортным.