Файлы прямо в бинаре. Go Embed
- понедельник, 19 января 2026 г. в 00:00:09
Стандартная ситуация - в вашем небольшом проекте на Go есть файлы переводов, картинки, миграции, html/gohtml темплейты, которые всегда должны быть рядом с проектом.
Как бы странным это не казалось, но ни раз я видел, как люди упорно пытаются рядом со сбилженым бинарем положить эти файлики в нужной форме, мучают девопсов и насилуют пайпы. Пошла эта мода скорее из некомпилируемых языков, к примеру Java Script, который на месте своего нахождения и без этого плодит помойку из каталогов.
Краткое, быстрое и элегантное решение — Embed.
Кейс — у нас сервис авторизации, который будет отправлять email после
Регистрации
Авторизации
Восстановления пароля
и тд
И для этого используем шаблоны. Чаще всего в таких случаях я видел что-то подобное:
package emails
import (...)
var emailTemplates *template.Template
func LoadEmailTemplates() {
emailTemplates = template.Must(
template.ParseGlob("templates/ru/*.gohtml"),
)
template.Must(
emailTemplates.ParseGlob("templates/en/*.gohtml"),
)
}Начинаем тестить — пишем cd cmd & go run .
Иии... Ничо не работает. Сразу понимаем проблему — пути все относительны. Тогда запускаем из корня, что остается делать.
После go run cmd/main.go все замечательно. Билдим, заливаем, тестим — все сломалось, получаем от пустых сообщений до паник.
Оказывается, что при go build в стандартном виде, в бинарь не идут файлы, которые не импортируются вашим main и их детьми. Единственный очевидный для нас способ — умолять девопса положить рядом эти файлики и каждый раз их обновлять. А если он добрый, то даже и пайп для этого красивый и удобный напишет. А уже на стенде при очередном корявом rollback этот же девопс будет вспоминать вас и ваших родных.
Концепция крайнепроста — мы создаем виртуальную файловую систему, которая будет всегда в нашем бинаре. Сразу к коду:
package emails
import (...)
//go:embed templates/**/*.gohtml
var templates embed.FS
var emailTemplates *template.Template
func LoadEmailTemplates() {
emailTemplates = template.Must(
template.ParseFS(
templates,
"templates/ru/*.gohtml",
"templates/en/*.gohtml",
),
)
}
Вся магия происходит на 5й строчке — с помощью аннотации директивы компилятора. Мы прямо говорим, чтобы все файлы по данному пути мы себе сохранили при билде в бинарь. Сам доступ к этой FS (File System) будет осуществляться через переменную типа embed.FS
Должен отметить, что встраивать таким образом мы можем не только каталоги и единичные файлы, но и привычные структуры данных: string, []byte и тп.
Немало важной особенностью embed.FS является реализация интерфейса fs.FS, а значит работает со всем стандартным стеком (в т.ч. fstest).
Есть некоторые нюансы данной директивы, к примеру нейминг путей, о которых можете прочитать в доке самой либы.
Теперь при любом билде наши шаблоны будут всегда рядом.
Самое очевидное — наш исполнительный файл значительно толстеет. Не столько это минус, так как чаще всего без нужных импортированных файлов этот бинарь и не нужен. Думаю, что и говорить не надо, что как еще одно S3 хранилище использовать такое нет надобности.
Вся эта виртуальная файловая система выгружается сразу в память. Плохо будет в основном для маленьких контейнеров. Но опять же, часто ли вы постоянно перечитываете, а не храните в виде кеша нужные файлы? Если вам полезен LazyLoad и для вас все это критично, то стоит задуматься.
Embed — compile‑time контракт. Если вам требуется что‑то динамичное, то такой способ вам явно не подойдет (тем не менее, все еще хранить это в файловой системе не самый лучший вариант).
Атомарность любого деплоя/роллбэка
Самодостаточность бинаря
Явный контракт каталогов (в виде директивы)
Отсутствие надобности очем‑то с кем‑то договариваться — мы теперь сами все сделали)
Кейсов использования достаточно много, некоторые из них уже используются и работают в готовых библиотеках. Мы можем хранить
Конфиги (естественно не все и всё)
Шаблоны
Остальную Статику HTML
Миграции
Swagger
Локализация
Тестирование
Есть множество флоу хранения миграций, локализаций, шаблонов и остальных статичных файлов (от бд до того же S3), но, часто наблюдая такую картину, решил напомнить всем о существовании прикольного инструмента, написав об этом свою первую статью.
Буду рад любым советам, критике или другим формам помощи в моем пути в комментариях
Также буду благодарен за подписку на мой тгк, если вам близки такие мини статьи и факты из моего личного опыта работы.
Спасибо за прочтение!