Skip to content

Commit 968b54d

Browse files
committed
feat: add Docker support with GitHub Container Registry
- Add Dockerfile with multi-stage build using Go 1.23 and scratch base - Add .dockerignore to optimize build context - Add docker-compose.yml for local development - Modify main.go to support --help flag for Docker usage - Update CI workflow to build and push Docker images to GHCR - Add dedicated Docker workflow with build tests and multi-platform support - Update README.md with comprehensive Docker usage examples and badges - Support for linux/amd64 and linux/arm64 platforms - Docker images tagged with version, latest, and edge variants - Includes build-time version injection and metadata labels
1 parent a000398 commit 968b54d

File tree

7 files changed

+513
-2
lines changed

7 files changed

+513
-2
lines changed

.dockerignore

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Git
2+
.git
3+
.gitignore
4+
.gitattributes
5+
6+
# CI/CD
7+
.github
8+
.codecov.yml
9+
10+
# Documentation
11+
README.md
12+
*.md
13+
docs/
14+
15+
# Build artifacts
16+
build/
17+
dist/
18+
*.exe
19+
*.tar.gz
20+
*.zip
21+
22+
# Test files
23+
*_test.go
24+
test_*.go
25+
test/
26+
coverage.out
27+
coverage.html
28+
*.log
29+
30+
# Development
31+
.vscode/
32+
.idea/
33+
*.swp
34+
*.swo
35+
*~
36+
37+
# OS specific
38+
.DS_Store
39+
Thumbs.db
40+
41+
# Output and temporary files
42+
output/
43+
tmp/
44+
temp/
45+
46+
# Node.js (if any)
47+
node_modules/
48+
npm-debug.log
49+
yarn-error.log
50+
51+
# Python (if any)
52+
__pycache__/
53+
*.pyc
54+
*.pyo
55+
*.pyd
56+
.Python
57+
env/
58+
venv/
59+
60+
# Scripts (build scripts not needed in container)
61+
scripts/
62+
63+
# Sample files
64+
articulate-sample.json
65+
test_input.json
66+
67+
# License
68+
LICENSE

.github/workflows/ci.yml

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: CI
22

33
on:
44
push:
5-
branches: [ "master", "develop" ]
5+
branches: [ "master", "develop", "feature/docker*" ]
66
tags:
77
- "v*.*.*"
88
pull_request:
@@ -359,3 +359,86 @@ jobs:
359359
prerelease: ${{ startsWith(github.ref, 'refs/tags/v0.') }}
360360
env:
361361
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
362+
363+
docker:
364+
name: Docker Build & Push
365+
runs-on: ubuntu-latest
366+
permissions:
367+
contents: read
368+
packages: write
369+
needs: [ "test" ]
370+
if: github.event_name == 'push' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop' || startsWith(github.ref, 'refs/tags/') || startsWith(github.ref, 'refs/heads/feature/docker'))
371+
steps:
372+
- name: Checkout repository
373+
uses: actions/checkout@v4
374+
375+
- name: Set up Docker Buildx
376+
uses: docker/setup-buildx-action@v3
377+
378+
- name: Log in to GitHub Container Registry
379+
uses: docker/login-action@v3
380+
with:
381+
registry: ghcr.io
382+
username: ${{ github.actor }}
383+
password: ${{ secrets.GITHUB_TOKEN }}
384+
385+
- name: Extract metadata
386+
id: meta
387+
uses: docker/metadata-action@v5
388+
with:
389+
images: ghcr.io/${{ github.repository }}
390+
tags: |
391+
type=ref,event=branch
392+
type=ref,event=pr
393+
type=semver,pattern={{version}}
394+
type=semver,pattern={{major}}.{{minor}}
395+
type=semver,pattern={{major}}
396+
type=raw,value=latest,enable={{is_default_branch}}
397+
labels: |
398+
org.opencontainers.image.title=Articulate Parser
399+
org.opencontainers.image.description=A tool to parse Articulate Rise courses and export them to various formats
400+
org.opencontainers.image.vendor=kjanat
401+
org.opencontainers.image.licenses=MIT
402+
403+
- name: Build and push Docker image
404+
uses: docker/build-push-action@v6
405+
with:
406+
context: .
407+
platforms: linux/amd64,linux/arm64
408+
push: true
409+
attest: true
410+
sbom: true
411+
tags: ${{ steps.meta.outputs.tags }}
412+
labels: ${{ steps.meta.outputs.labels }}
413+
build-args: |
414+
VERSION=${{ github.ref_type == 'tag' && github.ref_name || github.sha }}
415+
BUILD_TIME=${{ github.event.head_commit.timestamp }}
416+
GIT_COMMIT=${{ github.sha }}
417+
cache-from: type=gha
418+
cache-to: type=gha,mode=max
419+
420+
- name: Generate Docker summary
421+
run: |
422+
echo "## 🐳 Docker Build Summary" >> $GITHUB_STEP_SUMMARY
423+
echo "" >> $GITHUB_STEP_SUMMARY
424+
echo "**Image:** \`ghcr.io/${{ github.repository }}\`" >> $GITHUB_STEP_SUMMARY
425+
echo "" >> $GITHUB_STEP_SUMMARY
426+
echo "**Tags built:**" >> $GITHUB_STEP_SUMMARY
427+
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
428+
echo "${{ steps.meta.outputs.tags }}" >> $GITHUB_STEP_SUMMARY
429+
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
430+
echo "" >> $GITHUB_STEP_SUMMARY
431+
echo "**Platforms:** linux/amd64, linux/arm64" >> $GITHUB_STEP_SUMMARY
432+
echo "" >> $GITHUB_STEP_SUMMARY
433+
echo "**Usage:**" >> $GITHUB_STEP_SUMMARY
434+
echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY
435+
echo "# Pull the image" >> $GITHUB_STEP_SUMMARY
436+
echo "docker pull ghcr.io/${{ github.repository }}:latest" >> $GITHUB_STEP_SUMMARY
437+
echo "" >> $GITHUB_STEP_SUMMARY
438+
echo "# Run with help" >> $GITHUB_STEP_SUMMARY
439+
echo "docker run --rm ghcr.io/${{ github.repository }}:latest --help" >> $GITHUB_STEP_SUMMARY
440+
echo "" >> $GITHUB_STEP_SUMMARY
441+
echo "# Process a local file (mount current directory)" >> $GITHUB_STEP_SUMMARY
442+
echo "docker run --rm -v \$(pwd):/workspace ghcr.io/${{ github.repository }}:latest /workspace/input.json markdown /workspace/output.md" >> $GITHUB_STEP_SUMMARY
443+
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
444+
echo "" >> $GITHUB_STEP_SUMMARY

.github/workflows/docker.yml

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
name: Docker
2+
3+
on:
4+
push:
5+
branches: [ "master", "develop", "feature/docker-ghcr" ]
6+
tags:
7+
- "v*.*.*"
8+
pull_request:
9+
branches: [ "master", "develop" ]
10+
paths:
11+
- "Dockerfile"
12+
- ".dockerignore"
13+
- "go.mod"
14+
- "go.sum"
15+
- "**.go"
16+
workflow_dispatch:
17+
18+
env:
19+
REGISTRY: ghcr.io
20+
IMAGE_NAME: ${{ github.repository }}
21+
22+
jobs:
23+
docker-test:
24+
name: Docker Build Test
25+
runs-on: ubuntu-latest
26+
if: github.event_name == 'pull_request'
27+
permissions:
28+
contents: read
29+
steps:
30+
- name: Checkout repository
31+
uses: actions/checkout@v4
32+
33+
- name: Set up Docker Buildx
34+
uses: docker/setup-buildx-action@v3
35+
36+
- name: Build Docker image (test)
37+
uses: docker/build-push-action@v6
38+
with:
39+
context: .
40+
push: false
41+
tags: test:latest
42+
build-args: |
43+
VERSION=test
44+
BUILD_TIME=${{ github.event.head_commit.timestamp }}
45+
GIT_COMMIT=${{ github.sha }}
46+
cache-from: type=gha
47+
cache-to: type=gha,mode=max
48+
49+
- name: Test Docker image
50+
run: |
51+
echo "## 🧪 Docker Image Tests" >> $GITHUB_STEP_SUMMARY
52+
echo "" >> $GITHUB_STEP_SUMMARY
53+
54+
# Test that the image runs and shows help
55+
echo "**Testing help command:**" >> $GITHUB_STEP_SUMMARY
56+
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
57+
docker run --rm test:latest --help >> $GITHUB_STEP_SUMMARY
58+
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
59+
echo "" >> $GITHUB_STEP_SUMMARY
60+
61+
# Test image size
62+
IMAGE_SIZE=$(docker image inspect test:latest --format='{{.Size}}' | numfmt --to=iec-i --suffix=B)
63+
echo "**Image size:** $IMAGE_SIZE" >> $GITHUB_STEP_SUMMARY
64+
echo "" >> $GITHUB_STEP_SUMMARY
65+
66+
docker-build-and-push:
67+
name: Docker Build & Push
68+
runs-on: ubuntu-latest
69+
if: github.event_name != 'pull_request'
70+
permissions:
71+
contents: read
72+
packages: write
73+
steps:
74+
- name: Checkout repository
75+
uses: actions/checkout@v4
76+
77+
- name: Set up Docker Buildx
78+
uses: docker/setup-buildx-action@v3
79+
80+
- name: Log in to Container Registry
81+
uses: docker/login-action@v3
82+
with:
83+
registry: ${{ env.REGISTRY }}
84+
username: ${{ github.actor }}
85+
password: ${{ secrets.GITHUB_TOKEN }}
86+
87+
- name: Extract metadata
88+
id: meta
89+
uses: docker/metadata-action@v5
90+
with:
91+
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
92+
tags: |
93+
type=ref,event=branch
94+
type=ref,event=pr
95+
type=semver,pattern={{version}}
96+
type=semver,pattern={{major}}.{{minor}}
97+
type=semver,pattern={{major}}
98+
type=raw,value=latest,enable={{is_default_branch}}
99+
type=raw,value=edge,enable={{is_default_branch}}
100+
labels: |
101+
org.opencontainers.image.title=Articulate Parser
102+
org.opencontainers.image.description=A tool to parse Articulate Rise courses and export them to various formats
103+
org.opencontainers.image.vendor=kjanat
104+
org.opencontainers.image.licenses=MIT
105+
106+
- name: Build and push Docker image
107+
id: build
108+
uses: docker/build-push-action@v6
109+
with:
110+
context: .
111+
platforms: linux/amd64,linux/arm64
112+
push: true
113+
tags: ${{ steps.meta.outputs.tags }}
114+
labels: ${{ steps.meta.outputs.labels }}
115+
build-args: |
116+
VERSION=${{ github.ref_type == 'tag' && github.ref_name || github.sha }}
117+
BUILD_TIME=${{ github.event.head_commit.timestamp }}
118+
GIT_COMMIT=${{ github.sha }}
119+
cache-from: type=gha
120+
cache-to: type=gha,mode=max
121+
122+
- name: Generate Docker summary
123+
run: |
124+
echo "## 🐳 Docker Build & Push Summary" >> $GITHUB_STEP_SUMMARY
125+
echo "" >> $GITHUB_STEP_SUMMARY
126+
echo "**Registry:** \`${{ env.REGISTRY }}\`" >> $GITHUB_STEP_SUMMARY
127+
echo "**Image:** \`${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}\`" >> $GITHUB_STEP_SUMMARY
128+
echo "**Digest:** \`${{ steps.build.outputs.digest }}\`" >> $GITHUB_STEP_SUMMARY
129+
echo "" >> $GITHUB_STEP_SUMMARY
130+
echo "**Tags pushed:**" >> $GITHUB_STEP_SUMMARY
131+
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
132+
echo "${{ steps.meta.outputs.tags }}" >> $GITHUB_STEP_SUMMARY
133+
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
134+
echo "" >> $GITHUB_STEP_SUMMARY
135+
echo "**Platforms:** linux/amd64, linux/arm64" >> $GITHUB_STEP_SUMMARY
136+
echo "" >> $GITHUB_STEP_SUMMARY
137+
echo "### 📦 Usage Examples" >> $GITHUB_STEP_SUMMARY
138+
echo "" >> $GITHUB_STEP_SUMMARY
139+
echo "**Pull the latest image:**" >> $GITHUB_STEP_SUMMARY
140+
echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY
141+
echo "docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest" >> $GITHUB_STEP_SUMMARY
142+
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
143+
echo "" >> $GITHUB_STEP_SUMMARY
144+
echo "**Show help:**" >> $GITHUB_STEP_SUMMARY
145+
echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY
146+
echo "docker run --rm ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest --help" >> $GITHUB_STEP_SUMMARY
147+
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
148+
echo "" >> $GITHUB_STEP_SUMMARY
149+
echo "**Process a local file:**" >> $GITHUB_STEP_SUMMARY
150+
echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY
151+
echo "# Mount current directory and process file" >> $GITHUB_STEP_SUMMARY
152+
echo "docker run --rm -v \$(pwd):/workspace \\" >> $GITHUB_STEP_SUMMARY
153+
echo " ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest \\" >> $GITHUB_STEP_SUMMARY
154+
echo " /workspace/input.json markdown /workspace/output.md" >> $GITHUB_STEP_SUMMARY
155+
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
156+
echo "" >> $GITHUB_STEP_SUMMARY
157+
echo "**Process from URL:**" >> $GITHUB_STEP_SUMMARY
158+
echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY
159+
echo "# Mount output directory and process from URL" >> $GITHUB_STEP_SUMMARY
160+
echo "docker run --rm -v \$(pwd):/workspace \\" >> $GITHUB_STEP_SUMMARY
161+
echo " ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest \\" >> $GITHUB_STEP_SUMMARY
162+
echo " https://rise.articulate.com/share/xyz docx /workspace/output.docx" >> $GITHUB_STEP_SUMMARY
163+
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
164+
echo "" >> $GITHUB_STEP_SUMMARY
165+
166+
- name: Test published image
167+
run: |
168+
echo "## 🧪 Published Image Tests" >> $GITHUB_STEP_SUMMARY
169+
echo "" >> $GITHUB_STEP_SUMMARY
170+
171+
# Test that the published image works
172+
echo "**Testing published image:**" >> $GITHUB_STEP_SUMMARY
173+
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
174+
docker run --rm ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest --help >> $GITHUB_STEP_SUMMARY
175+
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
176+
echo "" >> $GITHUB_STEP_SUMMARY

Dockerfile

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Build stage
2+
FROM golang:1.23-alpine AS builder
3+
4+
# Install git and ca-certificates (needed for fetching dependencies and HTTPS)
5+
RUN apk add --no-cache git ca-certificates tzdata
6+
7+
# Set the working directory
8+
WORKDIR /app
9+
10+
# Copy go mod files
11+
COPY go.mod go.sum ./
12+
13+
# Download dependencies
14+
RUN go mod download
15+
16+
# Copy source code
17+
COPY . .
18+
19+
# Build the application
20+
# Disable CGO for a fully static binary
21+
# Use linker flags to reduce binary size and embed version info
22+
ARG VERSION=dev
23+
ARG BUILD_TIME
24+
ARG GIT_COMMIT
25+
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
26+
-ldflags="-s -w -X github.com/kjanat/articulate-parser/internal/version.Version=${VERSION} -X github.com/kjanat/articulate-parser/internal/version.BuildTime=${BUILD_TIME} -X github.com/kjanat/articulate-parser/internal/version.GitCommit=${GIT_COMMIT}" \
27+
-o articulate-parser \
28+
./main.go
29+
30+
# Final stage - minimal runtime image
31+
FROM scratch
32+
33+
# Copy CA certificates for HTTPS requests
34+
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
35+
36+
# Copy timezone data
37+
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
38+
39+
# Copy the binary
40+
COPY --from=builder /app/articulate-parser /articulate-parser
41+
42+
# Create a non-root user (note: in scratch image, we can't create users dynamically)
43+
# The binary will run as root in scratch, which is acceptable for containers
44+
45+
# Set the binary as entrypoint
46+
ENTRYPOINT ["/articulate-parser"]
47+
48+
# Default command shows help
49+
CMD ["--help"]
50+
51+
# Add labels for metadata
52+
LABEL org.opencontainers.image.title="Articulate Parser"
53+
LABEL org.opencontainers.image.description="A tool to parse Articulate Rise courses and export them to various formats"
54+
LABEL org.opencontainers.image.vendor="kjanat"
55+
LABEL org.opencontainers.image.licenses="MIT"
56+
LABEL org.opencontainers.image.source="https://github.com/kjanat/articulate-parser"
57+
LABEL org.opencontainers.image.documentation="https://github.com/kjanat/articulate-parser/blob/master/README.md"

0 commit comments

Comments
 (0)