🤖 ci: optimize CI/CD with Docker cache and remove Buildx
This commit is contained in:
@@ -143,9 +143,6 @@ jobs:
|
|||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
- name: Login to Gitea Container Registry
|
- name: Login to Gitea Container Registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
@@ -162,72 +159,40 @@ jobs:
|
|||||||
if docker pull "$IMAGE_NAME" >/dev/null 2>&1; then
|
if docker pull "$IMAGE_NAME" >/dev/null 2>&1; then
|
||||||
echo "✅ Using Docker build cache"
|
echo "✅ Using Docker build cache"
|
||||||
echo "CACHE_AVAILABLE=true" >> $GITHUB_ENV
|
echo "CACHE_AVAILABLE=true" >> $GITHUB_ENV
|
||||||
|
echo "CACHE_IMAGE=$IMAGE_NAME" >> $GITHUB_ENV
|
||||||
else
|
else
|
||||||
echo "⚠️ Building without cache (first run or new dependencies)"
|
echo "⚠️ Building without cache (first run or new dependencies)"
|
||||||
echo "CACHE_AVAILABLE=false" >> $GITHUB_ENV
|
echo "CACHE_AVAILABLE=false" >> $GITHUB_ENV
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Generate Swagger Docs using Docker cache
|
||||||
uses: actions/setup-go@v4
|
|
||||||
with:
|
|
||||||
go-version: '1.26.1'
|
|
||||||
cache: true
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: go mod tidy
|
|
||||||
|
|
||||||
# SINGLE swag installation - reused for all steps
|
|
||||||
- name: Install swag (once)
|
|
||||||
run: go install github.com/swaggo/swag/cmd/swag@latest
|
|
||||||
|
|
||||||
- name: Version bump (main branch only)
|
|
||||||
if: github.ref == 'refs/heads/main'
|
|
||||||
run: |
|
|
||||||
# Analyze last commit message
|
|
||||||
LAST_COMMIT=$(git log -1 --pretty=%B | head -1)
|
|
||||||
|
|
||||||
# Automatic version bump based on commit type
|
|
||||||
if echo "$LAST_COMMIT" | grep -q "^✨ feat:"; then
|
|
||||||
echo "🎯 Feature commit detected - bumping MINOR version"
|
|
||||||
./scripts/version-bump.sh minor
|
|
||||||
elif echo "$LAST_COMMIT" | grep -q "^🐛 fix:"; then
|
|
||||||
echo "🐛 Fix commit detected - bumping PATCH version"
|
|
||||||
./scripts/version-bump.sh patch
|
|
||||||
elif echo "$LAST_COMMIT" | grep -q "BREAKING CHANGE"; then
|
|
||||||
echo "💥 Breaking change detected - bumping MAJOR version"
|
|
||||||
./scripts/version-bump.sh major
|
|
||||||
else
|
|
||||||
echo "⏭️ No automatic version bump needed"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Generate Swagger Docs
|
|
||||||
run: |
|
run: |
|
||||||
if [ "${{ env.CACHE_AVAILABLE }}" = "true" ]; then
|
if [ "${{ env.CACHE_AVAILABLE }}" = "true" ]; then
|
||||||
echo "Running in Docker cache..."
|
echo "Running in Docker cache..."
|
||||||
docker run --rm -v "$(pwd):/workspace" -w /workspace dance-lessons-coach-build-cache:latest sh -c "cd pkg/server && go generate"
|
docker run --rm -v "$(pwd):/workspace" -w /workspace ${{ env.CACHE_IMAGE }} sh -c "cd pkg/server && go generate"
|
||||||
else
|
else
|
||||||
echo "Running natively..."
|
echo "Running natively..."
|
||||||
cd pkg/server && go generate
|
cd pkg/server && go generate
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Build all packages
|
- name: Build all packages using Docker cache
|
||||||
run: |
|
run: |
|
||||||
if [ "${{ env.CACHE_AVAILABLE }}" = "true" ]; then
|
if [ "${{ env.CACHE_AVAILABLE }}" = "true" ]; then
|
||||||
echo "Running in Docker cache..."
|
echo "Running in Docker cache..."
|
||||||
docker run --rm -v "$(pwd):/workspace" -w /workspace dance-lessons-coach-build-cache:latest sh -c "go build ./..."
|
docker run --rm -v "$(pwd):/workspace" -w /workspace ${{ env.CACHE_IMAGE }} sh -c "go build ./..."
|
||||||
else
|
else
|
||||||
echo "Running natively..."
|
echo "Running natively..."
|
||||||
go build ./...
|
go build ./...
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Run tests with coverage
|
- name: Run tests with coverage using Docker cache
|
||||||
run: |
|
run: |
|
||||||
if [ "${{ env.CACHE_AVAILABLE }}" = "true" ]; then
|
if [ "${{ env.CACHE_AVAILABLE }}" = "true" ]; then
|
||||||
echo "Running in Docker cache..."
|
echo "Running in Docker cache..."
|
||||||
docker run --rm \
|
docker run --rm \
|
||||||
-v "$(pwd):/workspace" \
|
-v "$(pwd):/workspace" \
|
||||||
-w /workspace \
|
-w /workspace \
|
||||||
dance-lessons-coach-build-cache:latest \
|
${{ env.CACHE_IMAGE }} \
|
||||||
sh -c "go test ./... -coverprofile=coverage.out -v && go tool cover -func=coverage.out > coverage.txt"
|
sh -c "go test ./... -coverprofile=coverage.out -v && go tool cover -func=coverage.out > coverage.txt"
|
||||||
else
|
else
|
||||||
echo "Running natively..."
|
echo "Running natively..."
|
||||||
@@ -271,10 +236,6 @@ jobs:
|
|||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.PACKAGES_TOKEN }}
|
password: ${{ secrets.PACKAGES_TOKEN }}
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
if: github.ref == 'refs/heads/main'
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
- name: Build and push Docker image
|
- name: Build and push Docker image
|
||||||
if: github.ref == 'refs/heads/main'
|
if: github.ref == 'refs/heads/main'
|
||||||
run: |
|
run: |
|
||||||
@@ -283,7 +244,9 @@ jobs:
|
|||||||
|
|
||||||
TAGS="$IMAGE_VERSION latest ${{ github.sha }}"
|
TAGS="$IMAGE_VERSION latest ${{ github.sha }}"
|
||||||
echo "Building Docker image with tags: $TAGS"
|
echo "Building Docker image with tags: $TAGS"
|
||||||
docker build -t dance-lessons-coach .
|
|
||||||
|
# Use the production Dockerfile that leverages the build cache
|
||||||
|
docker build -t dance-lessons-coach -f Dockerfile.prod .
|
||||||
|
|
||||||
for TAG in $TAGS; do
|
for TAG in $TAGS; do
|
||||||
IMAGE_NAME="${{ env.CI_REGISTRY }}/${{ env.GITEA_ORG }}/${{ env.GITEA_REPO }}:$TAG"
|
IMAGE_NAME="${{ env.CI_REGISTRY }}/${{ env.GITEA_ORG }}/${{ env.GITEA_REPO }}:$TAG"
|
||||||
|
|||||||
35
Dockerfile.prod
Normal file
35
Dockerfile.prod
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# DanceLessonsCoach Production Docker Image
|
||||||
|
# Minimal image using pre-built binary from CI cache
|
||||||
|
|
||||||
|
# Use the build cache image as base
|
||||||
|
FROM gitea.arcodange.lab/arcodange/dance-lessons-coach-build-cache:latest AS builder
|
||||||
|
|
||||||
|
# Final minimal image
|
||||||
|
FROM alpine:3.18
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install minimal dependencies
|
||||||
|
RUN apk add --no-cache ca-certificates tzdata
|
||||||
|
|
||||||
|
# Copy binary from builder
|
||||||
|
COPY --from=builder /workspace/dance-lessons-coach /app/dance-lessons-coach
|
||||||
|
|
||||||
|
# Copy configuration
|
||||||
|
COPY config.yaml /app/config.yaml
|
||||||
|
|
||||||
|
# Set permissions
|
||||||
|
RUN chmod +x /app/dance-lessons-coach
|
||||||
|
|
||||||
|
# Set timezone
|
||||||
|
ENV TZ=UTC
|
||||||
|
|
||||||
|
# Expose port
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
# Health check
|
||||||
|
HEALTHCHECK --interval=30s --timeout=3s \
|
||||||
|
CMD wget -q --spider http://localhost:8080/api/health || exit 1
|
||||||
|
|
||||||
|
# Entry point
|
||||||
|
ENTRYPOINT ["/app/dance-lessons-coach"]
|
||||||
@@ -31,12 +31,14 @@ This approach is simpler, more reliable, and works consistently with self-signed
|
|||||||
|
|
||||||
## Decision
|
## Decision
|
||||||
|
|
||||||
**Replace Docker Buildx with traditional docker build + push** for the CI/CD pipeline.
|
**Replace Docker Buildx with traditional docker build + push** for the CI/CD pipeline and implement a two-stage Docker build strategy.
|
||||||
|
|
||||||
### Implementation
|
### Implementation
|
||||||
|
|
||||||
|
#### 1. Build Cache Strategy
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
# New approach
|
# Build cache using traditional docker build
|
||||||
- name: Build and push Docker cache image
|
- name: Build and push Docker cache image
|
||||||
if: steps.check_cache.outputs.cache_hit == 'false'
|
if: steps.check_cache.outputs.cache_hit == 'false'
|
||||||
run: |
|
run: |
|
||||||
@@ -55,14 +57,121 @@ This approach is simpler, more reliable, and works consistently with self-signed
|
|||||||
echo "✅ Build cache image pushed successfully"
|
echo "✅ Build cache image pushed successfully"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### 2. Production Build Strategy
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Production build using Dockerfile.prod
|
||||||
|
- name: Build and push Docker image
|
||||||
|
if: github.ref == 'refs/heads/main'
|
||||||
|
run: |
|
||||||
|
source VERSION
|
||||||
|
IMAGE_VERSION="$MAJOR.$MINOR.$PATCH${PRERELEASE:+-$PRERELEASE}"
|
||||||
|
|
||||||
|
TAGS="$IMAGE_VERSION latest ${{ github.sha }}"
|
||||||
|
echo "Building Docker image with tags: $TAGS"
|
||||||
|
|
||||||
|
# Use the production Dockerfile that leverages the build cache
|
||||||
|
docker build -t dance-lessons-coach -f Dockerfile.prod .
|
||||||
|
|
||||||
|
for TAG in $TAGS; do
|
||||||
|
IMAGE_NAME="${{ env.CI_REGISTRY }}/${{ env.GITEA_ORG }}/${{ env.GITEA_REPO }}:$TAG"
|
||||||
|
echo "Tagging and pushing: $IMAGE_NAME"
|
||||||
|
docker tag dance-lessons-coach "$IMAGE_NAME"
|
||||||
|
docker push "$IMAGE_NAME"
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. Dockerfile Structure
|
||||||
|
|
||||||
|
**Dockerfile.build** - Build environment with all dependencies:
|
||||||
|
```dockerfile
|
||||||
|
FROM golang:1.26.1-alpine AS builder
|
||||||
|
|
||||||
|
# Install build dependencies
|
||||||
|
RUN apk add --no-cache git bash curl make gcc musl-dev bc grep sed jq ca-certificates
|
||||||
|
|
||||||
|
# Install Go tools
|
||||||
|
RUN go install github.com/swaggo/swag/cmd/swag@latest
|
||||||
|
|
||||||
|
# Copy and verify dependencies
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
RUN go mod download && go mod verify
|
||||||
|
|
||||||
|
WORKDIR /workspace
|
||||||
|
```
|
||||||
|
|
||||||
|
**Dockerfile.prod** - Minimal production image:
|
||||||
|
```dockerfile
|
||||||
|
# Use the build cache image as base
|
||||||
|
FROM gitea.arcodange.lab/arcodange/dance-lessons-coach-build-cache:latest AS builder
|
||||||
|
|
||||||
|
# Final minimal image
|
||||||
|
FROM alpine:3.18
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install minimal dependencies
|
||||||
|
RUN apk add --no-cache ca-certificates tzdata
|
||||||
|
|
||||||
|
# Copy binary from builder
|
||||||
|
COPY --from=builder /workspace/dance-lessons-coach /app/dance-lessons-coach
|
||||||
|
|
||||||
|
# Copy configuration
|
||||||
|
COPY config.yaml /app/config.yaml
|
||||||
|
|
||||||
|
# Set permissions and entrypoint
|
||||||
|
RUN chmod +x /app/dance-lessons-coach
|
||||||
|
ENV TZ=UTC
|
||||||
|
EXPOSE 8080
|
||||||
|
ENTRYPOINT ["/app/dance-lessons-coach"]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Dockerfile** - Development Dockerfile (kept for local development):
|
||||||
|
```dockerfile
|
||||||
|
# Multi-stage build for development
|
||||||
|
FROM golang:1.26.1-alpine AS builder
|
||||||
|
WORKDIR /app
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
RUN go mod download
|
||||||
|
COPY . ./
|
||||||
|
RUN go build -o /dance-lessons-coach ./cmd/server
|
||||||
|
|
||||||
|
FROM alpine:3.18
|
||||||
|
WORKDIR /app
|
||||||
|
RUN apk add --no-cache ca-certificates tzdata
|
||||||
|
COPY --from=builder /dance-lessons-coach /app/dance-lessons-coach
|
||||||
|
COPY config.yaml /app/config.yaml
|
||||||
|
RUN chmod +x /app/dance-lessons-coach
|
||||||
|
ENV TZ=UTC
|
||||||
|
EXPOSE 8080
|
||||||
|
ENTRYPOINT ["/app/dance-lessons-coach"]
|
||||||
|
```
|
||||||
|
|
||||||
## Benefits
|
## Benefits
|
||||||
|
|
||||||
|
### CI/CD Pipeline Benefits
|
||||||
|
|
||||||
1. **Simplicity**: Traditional approach is easier to understand and debug
|
1. **Simplicity**: Traditional approach is easier to understand and debug
|
||||||
2. **Reliability**: Consistent behavior across different environments
|
2. **Reliability**: Consistent behavior across different environments
|
||||||
3. **Certificate Handling**: Works seamlessly with self-signed certificates
|
3. **Certificate Handling**: Works seamlessly with self-signed certificates
|
||||||
4. **Performance**: Faster execution without Buildx overhead
|
4. **Performance**: Faster execution without Buildx overhead
|
||||||
5. **Compatibility**: Better compatibility with GitHub Actions environment
|
5. **Compatibility**: Better compatibility with GitHub Actions environment
|
||||||
|
|
||||||
|
### Two-Stage Build Benefits
|
||||||
|
|
||||||
|
1. **Separation of Concerns**: Clear separation between build environment and production runtime
|
||||||
|
2. **Optimized Production Image**: Minimal Alpine-based image with only necessary dependencies
|
||||||
|
3. **Reusable Build Cache**: Build environment can be reused across multiple CI runs
|
||||||
|
4. **Faster CI Execution**: Pre-built build cache reduces CI execution time
|
||||||
|
5. **Consistent Builds**: All builds use the same build environment
|
||||||
|
|
||||||
|
### Development vs Production Clarity
|
||||||
|
|
||||||
|
1. **Development Dockerfile**: Full build environment for local development
|
||||||
|
2. **Production Dockerfile**: Minimal runtime environment for deployment
|
||||||
|
3. **Build Cache Dockerfile**: Optimized build environment for CI/CD
|
||||||
|
4. **Clear Documentation**: Each Dockerfile has a specific purpose
|
||||||
|
|
||||||
## Trade-offs
|
## Trade-offs
|
||||||
|
|
||||||
### What We Lose
|
### What We Lose
|
||||||
|
|||||||
Reference in New Issue
Block a user