Skip to content

Commit a6b1e9b

Browse files
authored
feat: simplified multi-platform Docker builds (#12)
* fix: simplify Docker builds for testing - Disable minimal and whisper builds - Remove TARGET platform variables - Build only for linux/amd64 initially - Use simplest build configuration first * fix: remove suffix from Docker tags causing invalid format - Remove matrix.suffix from all tag types - Fixes 'invalid reference format' error in PR builds * fix: remove problematic SHA tag format - SHA tag with prefix was causing invalid tag format - Keep simple tags only for now * feat: add arm64 platform support back - Re-enable arm64 in both docker-build.yml and release.yml workflows - Add platform build arguments to Dockerfile (TARGETPLATFORM, TARGETOS, TARGETARCH) - Use dynamic linking for better multi-platform compatibility - Include opus runtime library in final image for arm64 support * docs: clarify Docker buildx platform argument usage - Update comments to clarify that TARGETPLATFORM, TARGETOS, and TARGETARCH are automatically set by Docker buildx based on the --platform flag - Apply consistent comment style across all Dockerfiles - No functional changes, just documentation improvements * refactor: remove unnecessary TARGETPLATFORM/OS/ARCH args - Docker buildx automatically handles cross-compilation via --platform flag - Removed explicit ARG declarations as they're not needed - Simplified Dockerfiles across the board - Build still works correctly for both linux/amd64 and linux/arm64 * feat: enable minimal Docker build for amd64 - Switch from main Dockerfile to minimal build in workflows - Minimal build creates ~12MB image using scratch base - Static linking only works for amd64 (opus library limitation on arm64) - Add -minimal suffix to Docker tags - Update labels to indicate minimal build without ffmpeg * feat: enable whisper Docker build for amd64 - Switch from minimal to whisper build in docker-build workflow - Whisper build includes whisper.cpp binaries for local transcription - Successfully builds for amd64 platform - Includes ffmpeg and whisper model support * feat: add arm64 support to whisper Docker build - Switch from static to dynamic linking for multi-platform compatibility - Include opus runtime library in final image - Enables whisper build for both linux/amd64 and linux/arm64 platforms * feat: enable all Docker builds with optimized caching - Enable all three Dockerfiles in CI matrix - Add main and whisper builds to release workflow - Optimize whisper build caching: - Move ARG before dependencies for better layer caching - Split clone and build into separate layers - Add build cache mount for whisper compilation - Add opus runtime library for dynamic linking - Main build: linux/amd64,linux/arm64 with ffmpeg - Minimal build: linux/amd64 only (static linking) - Whisper build: linux/amd64,linux/arm64 with whisper.cpp * fix: remove cache mount from whisper build The cache mount was causing issues with finding the built binaries. Keep the layer separation for caching but remove the problematic mount. * feat: set TRANSCRIBER_TYPE=whisper as default in whisper Docker image The whisper image includes whisper.cpp binaries, so it makes sense to default to using the whisper transcriber instead of the mock one. * fix: remove audio configuration env vars from Dockerfiles Audio processing defaults should be defined in code, not in Dockerfiles. Keep only TRANSCRIBER_TYPE=whisper in whisper image as it's image-specific. * refactor: simplify Docker strategy to two images - Remove Dockerfile.minimal (complexity not worth 38MB savings) - Remove ffmpeg from main image (not used in codebase) - Simplify to two images: standard and whisper - Both images now support linux/amd64 and linux/arm64 - Standard image ~15-20MB (was ~50MB with ffmpeg) - Better user experience with simpler choice * chore: remove debug ls command from Dockerfile.whisper Remove the 'ls -la build/bin/' debugging command as suggested by code review. The command was only used for debugging during development. * fix: remove Dockerfile.minimal from docker-lint workflow The minimal Dockerfile was removed but the CI workflow still tried to lint it, causing the docker-lint job to fail. * fix: resolve docker lint warnings in Dockerfile.whisper - Quote nproc to prevent word splitting (SC2046) - Consolidate multiple RUN commands to reduce layers (DL3059) - DL3018 already properly ignored with hadolint directive * fix: remove trailing spaces from hadolint ignore directive The hadolint ignore comment had trailing spaces which prevented it from being recognized properly.
1 parent 7e3c644 commit a6b1e9b

File tree

6 files changed

+54
-131
lines changed

6 files changed

+54
-131
lines changed

.github/workflows/ci.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,9 @@ jobs:
119119
dockerfile: Dockerfile
120120
failure-threshold: warning
121121

122-
- name: Run Hadolint on Dockerfile.minimal
122+
- name: Run Hadolint on Dockerfile.whisper
123123
uses: hadolint/[email protected]
124124
with:
125-
dockerfile: Dockerfile.minimal
126-
failure-threshold: warning
125+
dockerfile: Dockerfile.whisper
126+
failure-threshold: warning
127+

.github/workflows/docker-build.yml

Lines changed: 9 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,14 @@ jobs:
2525
strategy:
2626
matrix:
2727
include:
28+
# Standard Dockerfile
2829
- dockerfile: Dockerfile
2930
suffix: ""
3031
platforms: linux/amd64,linux/arm64
31-
- dockerfile: Dockerfile.minimal
32-
suffix: "-minimal"
32+
# Whisper Dockerfile with whisper.cpp for local transcription
33+
- dockerfile: Dockerfile.whisper
34+
suffix: "-whisper"
3335
platforms: linux/amd64,linux/arm64
34-
# Whisper Dockerfile disabled - experimental and requires fixes
35-
# - dockerfile: Dockerfile.whisper
36-
# suffix: "-whisper"
37-
# platforms: linux/amd64,linux/arm64
3836

3937
steps:
4038
- name: Checkout repository
@@ -66,19 +64,13 @@ jobs:
6664
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
6765
tags: |
6866
# For branches
69-
type=ref,event=branch,suffix=${{ matrix.suffix }}
70-
# For PRs
71-
type=ref,event=pr,suffix=${{ matrix.suffix }}
67+
type=ref,event=branch
68+
# For PRs
69+
type=ref,event=pr
7270
# For tags
73-
type=ref,event=tag,suffix=${{ matrix.suffix }}
71+
type=ref,event=tag
7472
# Latest tag for main branch
75-
type=raw,value=latest${{ matrix.suffix }},enable={{is_default_branch}}
76-
# Version tags
77-
type=semver,pattern={{version}},suffix=${{ matrix.suffix }}
78-
type=semver,pattern={{major}}.{{minor}},suffix=${{ matrix.suffix }}
79-
type=semver,pattern={{major}},suffix=${{ matrix.suffix }}
80-
# SHA
81-
type=sha,prefix={{branch}}-,suffix=${{ matrix.suffix }}
73+
type=raw,value=latest,enable={{is_default_branch}}
8274
flavor: |
8375
latest=false
8476
@@ -134,13 +126,6 @@ jobs:
134126
135127
docker manifest push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${TAG}-multiarch
136128
137-
# Create manifest for minimal image
138-
docker manifest create ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${TAG}-minimal-multiarch \
139-
--amend ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${TAG}-minimal-linux-amd64 \
140-
--amend ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${TAG}-minimal-linux-arm64
141-
142-
docker manifest push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${TAG}-minimal-multiarch
143-
144129
# Create manifest for whisper image
145130
docker manifest create ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${TAG}-whisper-multiarch \
146131
--amend ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${TAG}-whisper-linux-amd64 \

.github/workflows/release.yml

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ jobs:
4040
username: ${{ github.actor }}
4141
password: ${{ secrets.GITHUB_TOKEN }}
4242

43-
# Build and push normal image
44-
- name: Build and push normal Docker image
43+
# Build and push standard Docker image
44+
- name: Build and push standard Docker image
4545
uses: docker/build-push-action@v6
4646
with:
4747
context: .
@@ -53,27 +53,27 @@ jobs:
5353
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
5454
labels: |
5555
org.opencontainers.image.title=Discord Voice MCP
56-
org.opencontainers.image.description=Discord voice channel transcription with MCP integration
56+
org.opencontainers.image.description=Discord voice transcription MCP server
5757
org.opencontainers.image.version=${{ github.event.release.tag_name || github.event.inputs.tag }}
5858
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
5959
org.opencontainers.image.revision=${{ github.sha }}
6060
cache-from: type=gha
6161
cache-to: type=gha,mode=max
6262

63-
# Build and push minimal image
64-
- name: Build and push minimal Docker image
63+
# Build and push whisper image with local transcription
64+
- name: Build and push whisper Docker image
6565
uses: docker/build-push-action@v6
6666
with:
6767
context: .
68-
file: Dockerfile.minimal
68+
file: Dockerfile.whisper
6969
platforms: linux/amd64,linux/arm64
7070
push: true
7171
tags: |
72-
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.release.tag_name || github.event.inputs.tag }}-minimal
73-
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:minimal
72+
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.release.tag_name || github.event.inputs.tag }}-whisper
73+
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:whisper
7474
labels: |
75-
org.opencontainers.image.title=Discord Voice MCP (Minimal)
76-
org.opencontainers.image.description=Minimal Discord voice MCP without ffmpeg
75+
org.opencontainers.image.title=Discord Voice MCP (Whisper)
76+
org.opencontainers.image.description=Discord voice MCP with whisper.cpp for local transcription
7777
org.opencontainers.image.version=${{ github.event.release.tag_name || github.event.inputs.tag }}
7878
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
7979
org.opencontainers.image.revision=${{ github.sha }}

Dockerfile

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
11
# Build stage
22
FROM golang:1.24-alpine3.21 AS builder
33

4-
# Build arguments for target platform
5-
ARG TARGETPLATFORM
6-
ARG TARGETOS
7-
ARG TARGETARCH
8-
94
# Install build dependencies
105
# hadolint ignore=DL3018
116
RUN apk add --no-cache git gcc musl-dev pkgconfig opus-dev
@@ -19,17 +14,18 @@ RUN go mod download
1914
# Copy source code
2015
COPY . .
2116

22-
# Build static binary with CGO
23-
RUN CGO_ENABLED=1 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \
24-
go build -a -tags netgo -ldflags '-w -s -extldflags "-static"' \
17+
# Build binary with CGO
18+
# Docker buildx automatically handles cross-compilation via --platform flag
19+
# Using dynamic linking as static opus lib not available for all architectures
20+
RUN CGO_ENABLED=1 go build -ldflags '-w -s' \
2521
-o discord-voice-mcp ./cmd/discord-voice-mcp
2622

27-
# Final stage - using alpine for ffmpeg support
23+
# Final stage
2824
FROM alpine:3.20
2925

30-
# Install only ffmpeg (needed for audio processing)
26+
# Install opus runtime library (required for dynamic linking)
3127
# hadolint ignore=DL3018
32-
RUN apk add --no-cache ffmpeg
28+
RUN apk add --no-cache opus
3329

3430
WORKDIR /app
3531

@@ -42,14 +38,9 @@ USER mcp
4238

4339
# Note: No ports exposed as this uses stdin/stdout for MCP protocol
4440

45-
# Audio processing configuration (defaults)
46-
ENV AUDIO_BUFFER_DURATION_SEC=2 \
47-
AUDIO_SILENCE_TIMEOUT_MS=1500 \
48-
AUDIO_MIN_BUFFER_MS=100
49-
5041
# Run the binary
5142
CMD ["./discord-voice-mcp"]
5243

53-
# Expected image size: ~50MB (vs 2.35GB for Node.js version!)
44+
# Expected image size: ~15-20MB
5445
# Binary size: ~15MB
55-
# Alpine + ffmpeg: ~35MB
46+
# Alpine + opus: ~5MB

Dockerfile.minimal

Lines changed: 0 additions & 45 deletions
This file was deleted.

Dockerfile.whisper

Lines changed: 22 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
11
# Build stage for Go binary
22
FROM golang:1.24-alpine3.21 AS go-builder
33

4-
# Build arguments for target platform
5-
ARG TARGETPLATFORM
6-
ARG TARGETOS
7-
ARG TARGETARCH
8-
94
# Install build dependencies
105
# hadolint ignore=DL3018
116
RUN apk add --no-cache git gcc musl-dev pkgconfig opus-dev
@@ -19,35 +14,37 @@ RUN go mod download
1914
# Copy source code
2015
COPY . .
2116

22-
# Build static binary with CGO
23-
RUN CGO_ENABLED=1 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \
24-
go build -a -tags netgo -ldflags '-w -s -extldflags "-static"' \
17+
# Build binary with CGO
18+
# Using dynamic linking as static opus lib not available for all architectures
19+
RUN CGO_ENABLED=1 go build -ldflags '-w -s' \
2520
-o discord-voice-mcp ./cmd/discord-voice-mcp
2621

2722
# Build stage for whisper.cpp
2823
FROM alpine:3.20 AS whisper-builder
2924

25+
# Pin to a specific version for reproducible builds
26+
ARG WHISPER_CPP_VERSION=v1.7.3
27+
3028
# Install build dependencies for whisper.cpp (including cmake which is now required)
3129
# hadolint ignore=DL3018
3230
RUN apk add --no-cache git make g++ bash cmake
3331

34-
# Clone and build whisper.cpp
32+
# Clone whisper.cpp (separate layer for better caching)
3533
WORKDIR /build
36-
# Pin to a specific version for reproducible builds
37-
ARG WHISPER_CPP_VERSION=v1.7.3
38-
RUN git clone --depth 1 --branch ${WHISPER_CPP_VERSION} https://github.com/ggerganov/whisper.cpp.git && \
39-
cd whisper.cpp && \
40-
cmake -B build -DGGML_CCACHE=OFF . && \
41-
cmake --build build --config Release -- -j$(nproc) && \
42-
ls -la build/bin/ && \
34+
RUN git clone --depth 1 --branch ${WHISPER_CPP_VERSION} https://github.com/ggerganov/whisper.cpp.git
35+
36+
# Build whisper.cpp (this layer only rebuilds if source changes)
37+
WORKDIR /build/whisper.cpp
38+
RUN cmake -B build -DGGML_CCACHE=OFF . && \
39+
cmake --build build --config Release -- -j"$(nproc)" && \
4340
chmod +x build/bin/*
4441

4542
# Final stage
4643
FROM alpine:3.20
4744

48-
# Install runtime dependencies
49-
# hadolint ignore=DL3018
50-
RUN apk add --no-cache ffmpeg libstdc++ libgomp
45+
# Install runtime dependencies (including opus for dynamic linking)
46+
# hadolint ignore=DL3018
47+
RUN apk add --no-cache ffmpeg libstdc++ libgomp opus
5148

5249
WORKDIR /app
5350

@@ -60,20 +57,14 @@ COPY --from=whisper-builder /build/whisper.cpp/build/bin/server /usr/local/bin/w
6057
# Copy whisper shared libraries from correct directories
6158
COPY --from=whisper-builder /build/whisper.cpp/build/src/libwhisper.so* /usr/local/lib/
6259
COPY --from=whisper-builder /build/whisper.cpp/build/ggml/src/libggml*.so* /usr/local/lib/
63-
# Update library cache
64-
RUN ldconfig /usr/local/lib
65-
66-
# Create directories for models
67-
RUN mkdir -p /models
68-
69-
# Create non-root user
70-
RUN adduser -D -u 1000 mcp
60+
# Update library cache, create directories, and add user
61+
RUN ldconfig /usr/local/lib && \
62+
mkdir -p /models && \
63+
adduser -D -u 1000 mcp
7164
USER mcp
7265

73-
# Audio processing configuration (defaults)
74-
ENV AUDIO_BUFFER_DURATION_SEC=2 \
75-
AUDIO_SILENCE_TIMEOUT_MS=1500 \
76-
AUDIO_MIN_BUFFER_MS=100
66+
# Set default transcriber type for whisper image
67+
ENV TRANSCRIBER_TYPE=whisper
7768

7869
# Run the binary
7970
CMD ["./discord-voice-mcp"]

0 commit comments

Comments
 (0)