Skip to content
Merged
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
83 changes: 83 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
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

# Setup Docker Buildx to enable advanced build features and caching
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

# Build and cache Docker layers using GitHub Actions cache backend
# 'load: true' exports the image to the local Docker daemon for testing
- name: Build and Load Docker Image
uses: docker/build-push-action@v5
with:
context: .
load: true
tags: worker-ffmpeg:latest
cache-from: type=gha
cache-to: type=gha,mode=max

# Authenticate to GHCR for image publishing
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

# Fetch reference test asset (1080p H.264)
# Dynamic download avoids repository bloat
- name: Download Test Asset
run: |
mkdir -p video
echo "Downloading sample video..."
# Source: Big Buck Bunny (Stable URL)
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/

# Execute integration test suite
# Patch script to skip redundant build step (using cached image)
- name: Run Test Suite
env:
SKIP_BUILD: 'true'
run: |
chmod +x test/ffmpeg.test.sh
bash test/ffmpeg.test.sh

# Publish artifact to GHCR (Production branch only)
- name: Push to GHCR
if: github.ref == 'refs/heads/main'
run: |
IMAGE_ID=ghcr.io/${{ github.repository }}
# Normalize repository name to lowercase
IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]')

# Apply semantic tags (latest, SHA) for versioning
docker tag worker-ffmpeg:latest $IMAGE_ID:latest
docker tag worker-ffmpeg:latest $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.1.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