Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: CI

on:
push:
branches: [ "main", "master", 'dev']
pull_request:
branches: [ "main", "master", 'dev']

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
build-and-test:
name: Build & Test (Ubuntu)
runs-on: ubuntu-latest

permissions:
contents: read
packages: write

steps:
# 1. Checkout the repository code
- name: Checkout Code
uses: actions/checkout@v4

# 2. Log in to GitHub Container Registry
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GHCR_TOKEN }}

# 3. Download a sample test video (1080p, ~2MB)
# Dynamically to keep the git repo small.
# The script expects 'video', so we simulate that structure.
- name: Download Test Asset
run: |
mkdir -p video
echo "Downloading sample video..."
# Using a reliable test video source (Big Buck Bunny stable link)
curl -L -o video/test.mp4 https://test-videos.co.uk/vids/bigbuckbunny/mp4/h264/1080/Big_Buck_Bunny_1080_10s_5MB.mp4
ls -lh video/

# 4. Run the Test Script
# Since ffmpeg.test.sh handles 'docker build', we just run it directly.
- name: Run Test Suite
run: |
chmod +x test/ffmpeg.test.sh
bash test/ffmpeg.test.sh

# 5. Push Image to GHCR (Only on Main)
- name: Push to GHCR
if: github.ref == 'refs/heads/main'
run: |
IMAGE_ID=ghcr.io/${{ github.repository }}
# Change all uppercase to lowercase
IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]')

# Tag the image built by ffmpeg.test.sh with the proper GHCR name
docker tag worker-ffmpeg $IMAGE_ID:latest
docker tag worker-ffmpeg $IMAGE_ID:${{ github.sha }}

echo "Pushing $IMAGE_ID:latest"
docker push $IMAGE_ID:latest
docker push $IMAGE_ID:${{ github.sha }}
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,7 @@ dist
# Vite logs files
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

.DS_Store
video
output
130 changes: 130 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# ── STAGE 1: BUILDER ─────────────────────────────────────────────────────────
# Alpine 3.21 chosen for minimal footprint (~5MB base).
# We compile from source to control exactly which libraries are linked.
# ─────────────────────────────────────────────────────────────────────────────
FROM alpine:3.21 AS builder

# Link to GitHub Repository for Package Visibility
LABEL org.opencontainers.image.source=https://github.com/maulik-mk/mp.ii-worker

# 1. Install Build Dependencies
# We only install what's strictly necessary for compilation.
# - build-base: GCC, Make, libc-dev (standard build toolchain)
# - pkgconf: Helper for library path resolution
# - nasm/yasm: Assemblers required for x264 SIMD optimizations (CRITICAL for perf)
# - x264-dev: H.264 video encoder headers
# - fdk-aac-dev: High-quality AAC audio encoder headers (better than native aac)
RUN apk add --no-cache \
build-base \
pkgconf \
nasm \
yasm \
x264-dev \
fdk-aac-dev

# 2. Download FFmpeg Source
ARG FFMPEG_VERSION=7.1
RUN wget -q https://ffmpeg.org/releases/ffmpeg-${FFMPEG_VERSION}.tar.bz2 && \
tar xjf ffmpeg-${FFMPEG_VERSION}.tar.bz2 && \
rm ffmpeg-${FFMPEG_VERSION}.tar.bz2

WORKDIR /ffmpeg-${FFMPEG_VERSION}

# 3. Configure & Compile
# ONLY (H.264 + AAC).
#
# Flags explained:
# --enable-small: Optimize for size
# --disable-network: Attack surface reduction.
# --disable-autodetect: Deterministic build.
# --disable-*: We strip all GUI dependencies (SDL, X11, XCB) and hardware accelerators
# --extra-cflags: "-O2" for standard optimization. "-march=armv8-a" matches target arch.
RUN ./configure \
--prefix=/usr/local \
--enable-gpl \
--enable-nonfree \
--enable-small \
\
# ── Core Capabilities ── \
--enable-libx264 \
--enable-libfdk-aac \
\
# ── Bloat Removal Strategy ── \
--disable-doc \
--disable-debug \
--disable-ffplay \
--disable-network \
--disable-autodetect \
\
# ── GUI & System Dependencies Strip ── \
--disable-sdl2 \
--disable-libxcb \
--disable-libxcb-shm \
--disable-libxcb-xfixes \
--disable-libxcb-shape \
--disable-xlib \
\
# ── Hardware Acceleration Strip (CPU-only target) ── \
--disable-vaapi \
--disable-vdpau \
--disable-videotoolbox \
--disable-audiotoolbox \
--disable-cuda \
--disable-cuvid \
--disable-nvenc \
--disable-nvdec \
\
# ── Device Strip ── \
--disable-indevs \
--disable-outdevs \
\
# ── Compiler Optimizations ── \
--extra-cflags="-O2" \
\
&& make -j$(nproc) \
&& make install \
# Binary Stripping: Removes debug symbols (~80% size reduction on binary)
&& strip /usr/local/bin/ffmpeg /usr/local/bin/ffprobe

# ── STAGE 2: RUNTIME ─────────────────────────────────────────────────────────
FROM alpine:3.21

# 1. Install Runtime Dependencies
# These are the shared libraries our compiled FFmpeg binary links against.
# Without these, the binary will fail with "not found" errors.
# - x264-libs: H.264 runtime
# - fdk-aac: AAC runtime
# - ca-certificates: Required we ever need to fetch HTTPS or HTTP
# clean apk cache immediately to keep layer size minimal.
RUN apk add --no-cache \
x264-libs \
fdk-aac \
ca-certificates \
&& rm -rf /var/cache/apk/*

# 2. Copy Artifacts
# Bringing over ONLY the compiled binaries from Stage 1.
COPY --from=builder /usr/local/bin/ffmpeg /usr/local/bin/ffmpeg
COPY --from=builder /usr/local/bin/ffprobe /usr/local/bin/ffprobe

# 3. Security Hardening
# - Create specific directories for input/output to control scope.
# - Create a non-root 'ffmpeg' user/group.
# - Chown directories to this user.
# - Switch USER context.
# Ideally, we should run with read-only root filesystem if possible.
RUN mkdir -p /input /output && \
addgroup -S ffmpeg && adduser -S ffmpeg -G ffmpeg && \
chown -R ffmpeg:ffmpeg /input /output

USER ffmpeg
WORKDIR /work

# 4. Verification Step
# Fails the build immediately if the binary is broken/missing libs.
RUN ffmpeg -version && ffprobe -version

# Entrypoint configuration
# Allows passing arguments directly to docker run, e.g., "docker run img -i ..."
ENTRYPOINT ["ffmpeg"]
CMD ["-version"]
27 changes: 27 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "worker",
"version": "0.0.0",
"description": "FFmpeg worker",
"repository": {
"type": "git",
"url": "git+https://github.com/maulik-mk/mp.ii-worker.git"
},
"keywords": [
"ffmpeg",
"worker",
"video processing",
"video encoding",
"video decoding",
"video transcoding",
"video streaming",
"video processing worker",
"video processing worker",
"nodjs"
],
"author": "Maulik MK",
"license": "ISC",
"bugs": {
"url": "https://github.com/maulik-mk/mp.ii-worker/issues"
},
"homepage": "https://github.com/maulik-mk/mp.ii-worker#readme"
}
Loading