Каким образом делается взаимодействие с hardware в микросервисной архитектуре и Docker-контейнерах?
COM, USB, принтеры и какие-то специфичные железяки для терминалов - та часть, где код должен быть по определению железо-зависимым. Контейнеры же про диаметрально противоположное, про абстракцию от железа и окружения. Т.е. писать софт для специфичной железяки и запускать его в контейнере, это как в истории про микроскоп и гвозди. 
Вы сделали правильный вывод. Нужно выносить в контейнеры только логику независящую от какого-либо железа или оборудования. А сервисы, взаимодействующие с железом, разворачивать там где и железо.
Можно извернуться и сделать форвардинг COM\USB портов по TCP. Но это все будет работать нестабильно и через жопу. 
Все что относится к клиенту, должно работать на клиенте. Печать - ответственность только клиента, бэк в контейнере вообще ничего не должен об этом знать, только генерировать документы если надо. 
Работа внешним оборудованием тоже отвественность клиента. Если оборудование надо зашарить, то на клиенте подымается сервис.