پرش به محتوا
آرش حاتمی

hatamiarash7/ Docker-UPX

سال توسعه: ۱۴۰۱ متن‌باز

فشرده‌سازی فایل‌های اجرایی در Docker با UPX

ایمیج چندمعماری UPX برای کاهش حجم فایل‌های اجرایی، با امکان استفادهٔ مستقیم و افزودن مرحلهٔ فشرده‌سازی به ساخت چندمرحله‌ای Docker.

  • Docker
  • UPX
  • BusyBox
  • Alpine Linux
  • GitHub Actions

در برنامه‌هایی که به شکل یک فایل اجرایی منتشر می‌شوند، حجم باینری می‌تواند بخش بزرگی از حجم ایمیج Docker را تشکیل دهد. ساخت چندمرحله‌ای ابزارهای کامپایل را از ایمیج نهایی حذف می‌کند؛ برای کوچک‌ترکردن خود فایل اجرایی هم می‌توان از UPX استفاده کرد.

پروژهٔ Docker-UPX این ابزار را در قالب یک ایمیج مبتنی بر BusyBox در اختیار می‌گذارد. می‌توانید بدون نصب مستقیم UPX روی میزبان، فایل اجرایی را فشرده کنید یا این کار را به مرحله‌ای از فرایند ساخت Docker اضافه کنید. فایل خروجی برای اجرا به نصب جداگانهٔ UPX نیاز ندارد.

ساختار ایمیج

در Dockerfile پروژه، ابتدا یک مرحلهٔ Alpine نسخهٔ مناسب UPX را از انتشارهای رسمی آن دریافت می‌کند. سپس فایل اجرایی UPX به ایمیج BusyBox منتقل می‌شود؛ ابزارهای دانلود در این ایمیج نهایی باقی نمی‌مانند.

دایرکتوری کاری /workspace و نقطهٔ ورود کانتینر /bin/upx است. به همین دلیل گزینه‌هایی که بعد از نام ایمیج می‌نویسید، مستقیماً به UPX می‌رسند. اجرای کانتینر بدون گزینه نیز راهنمای ابزار را نمایش می‌دهد.

گردش‌کار انتشار برای پنج پلتفرم linux/amd64، linux/arm64، linux/arm/v7، linux/arm/v6 و linux/386 تنظیم شده و مقصدهای انتشار آن Docker Hub و GitHub Container Registry هستند. این فهرست، معماری اجرای کانتینر را مشخص می‌کند؛ پشتیبانی از قالب فایل ورودی موضوع جداگانه‌ای است و به UPX بستگی دارد.

استفادهٔ مستقیم

برای این روش به Docker با قابلیت اجرای کانتینرهای لینوکسی و یک فایل اجرایی سازگار با UPX نیاز دارید. ایمیج را از یکی از رجیستری‌های معرفی‌شده در راهنمای پروژه دریافت کنید:

docker pull hatamiarash7/upx:latest

آدرس جایگزین در GitHub Container Registry نیز به این شکل است:

docker pull ghcr.io/hatamiarash7/upx:latest

فرض کنید فایل اجرایی شما با نام app در دایرکتوری جاری قرار دارد. برای ساخت نسخهٔ فشرده با نام app-compressed اجرا کنید:

docker run --rm \
  -v "$PWD:/workspace" \
  hatamiarash7/upx:latest \
  --best --lzma \
  -o /workspace/app-compressed /workspace/app

دایرکتوری جاری داخل کانتینر به /workspace متصل می‌شود و فایل خروجی هم در همان دایرکتوری میزبان قرار می‌گیرد. گزینهٔ -o مسیر خروجی را جدا می‌کند تا فایل اصلی حفظ شود؛ --rm نیز کانتینر را پس از پایان کار حذف می‌کند.

گزینهٔ --best برای فشرده‌سازی بیشتر با صرف زمان بیشتر است و --lzma الگوریتم LZMA را انتخاب می‌کند. UPX سطح‌های -1 تا -9 و گزینه‌های جست‌وجوی بیشتر مانند --brute و --ultra-brute هم دارد. برای مشاهدهٔ راهنمای نسخهٔ داخل ایمیج:

docker run --rm hatamiarash7/upx:latest --help

استفاده در ساخت چندمرحله‌ای

کاربرد اصلی پروژه این است که بین مرحلهٔ کامپایل و ایمیج نهایی، یک مرحلهٔ فشرده‌سازی قرار بگیرد. نمونهٔ زیر، اقتباسی از مثال C در مستندات مخزن است و فرض می‌کند فایل main.c یک برنامهٔ ساده و بدون وابستگی اضافی در دایرکتوری ساخت دارید:

FROM alpine:3.24 AS builder

RUN apk add --no-cache gcc musl-dev
WORKDIR /src
COPY main.c .
RUN gcc -static -O3 -o /app main.c && strip /app

FROM hatamiarash7/upx:latest AS compressor

COPY --from=builder /app /workspace/app
RUN upx --best --lzma -o /workspace/app-compressed /workspace/app

FROM scratch

COPY --from=compressor /workspace/app-compressed /app
ENTRYPOINT ["/app"]

در مرحلهٔ اول، برنامه به صورت استاتیک ساخته می‌شود و اطلاعات نمادها با strip حذف می‌شوند. مرحلهٔ دوم فقط مسئول فشرده‌سازی است و مرحلهٔ سوم فایل حاصل را تحویل می‌گیرد. در نتیجه نه کامپایلر و نه ابزار UPX وارد ایمیج برنامه نمی‌شوند.

این الگو برای برنامه‌های Go و Rust هم در مثال‌های مخزن آمده است. استفاده از scratch فقط زمانی مناسب است که تمام نیازهای زمان اجرای برنامه را تأمین کرده باشید؛ فشرده‌سازی، وابستگی به کتابخانه‌های پویا، گواهی‌های TLS یا فایل‌های داده را از بین نمی‌برد.

ساخت ایمیج UPX با تنظیمات دلخواه

اگر بخواهید نسخهٔ ابزار یا ایمیج‌های پایه را انتخاب کنید، می‌توانید خود Docker-UPX را از سورس بسازید. تنظیمات با Build Argument دریافت می‌شوند، نه متغیر محیطی زمان اجرای کانتینر.

پیش‌فرض‌های Dockerfile بررسی‌شده عبارت‌اند از:

آرگومان مقدار پیش‌فرض کاربرد
UPX_VERSION latest نسخهٔ UPX برای دانلود
ALPINE_VERSION 3.24 نسخهٔ مرحلهٔ دریافت فایل
BUSYBOX_VERSION 1.38 نسخهٔ ایمیج نهایی ابزار

برای نمونه، دستور زیر نسخهٔ مشخص ۴٫۲٫۴ را که در راهنمای مخزن مثال زده شده انتخاب می‌کند:

git clone https://github.com/hatamiarash7/Docker-UPX.git
cd Docker-UPX
docker build --build-arg UPX_VERSION=4.2.4 -t my-upx .

این ساخت به دسترسی شبکه برای دریافت ایمیج‌های پایه، بسته‌های Alpine و انتشار UPX نیاز دارد. مقدار latest برای UPX_VERSION هنگام ساخت از انتشارهای UPX استخراج می‌شود؛ با تگ latest ایمیج آماده یکسان نیست و اجرای یک کانتینر، ابزار داخل آن را به‌روزرسانی نمی‌کند.

نکات استفاده

  • میزان کاهش حجم ثابت نیست. نتیجه به محتوا و قالب فایل و گزینه‌های فشرده‌سازی بستگی دارد؛ اندازهٔ خروجی واقعی پروژهٔ خودتان را مقایسه کنید.
  • خروجی را در محیط مقصد آزمایش کنید. پذیرش فایل توسط UPX به‌تنهایی جای آزمایش اجرای برنامه و وابستگی‌های آن را نمی‌گیرد. اثر فشرده‌سازی بر زمان شروع و مصرف حافظه را هم برای همان برنامه بسنجید.
  • باینری استاتیک برای مثال scratch اهمیت دارد. این شرطِ آن الگوی استقرار است؛ UPX به‌طور کلی فقط به فایل‌های استاتیک محدود نیست و چندین قالب اجرایی را پشتیبانی می‌کند.
  • برخی آنتی‌ویروس‌ها ممکن است هشدار کاذب بدهند. راهنمای پروژه این موضوع را برای فایل‌های بسته‌بندی‌شده با UPX یادآوری می‌کند.

کد مخزن Docker-UPX با مجوز MIT منتشر شده است. خود ابزار UPX مجوز جداگانهٔ GPL با استثنای ویژه برای فایل‌های اجرایی فشرده‌شده دارد؛ مجوز مخزن Docker جایگزین مجوز ابزار داخل آن نیست.