1
+ name : Build and Release Docker Images
2
+
3
+ on :
4
+ push :
5
+ branches :
6
+ - main
7
+ paths :
8
+ - ' apps/**'
9
+ # Only trigger on app changes or workflow changes for the starter
10
+ - ' .github/workflows/docker-build.yml'
11
+
12
+ workflow_dispatch :
13
+
14
+ permissions :
15
+ contents : write # Needed for creating tags/releases
16
+ packages : write # Needed for publishing packages to GHCR
17
+
18
+ jobs :
19
+ discover-services :
20
+ name : Discover Services
21
+ runs-on : ubuntu-latest
22
+ outputs :
23
+ services : ${{ steps.set-services.outputs.services }}
24
+ steps :
25
+ - name : Checkout code
26
+ uses : actions/checkout@v4
27
+ with :
28
+ fetch-depth : 2 # Need at least 2 commits to detect changes
29
+
30
+ - name : Find services with Dockerfiles and changes
31
+ id : set-services
32
+ run : |
33
+ # Determine if this is a manual trigger
34
+ IS_MANUAL="${{ github.event_name == 'workflow_dispatch' }}"
35
+
36
+ # Get list of changed files (compare with previous commit)
37
+ if [[ "$IS_MANUAL" == "true" ]]; then
38
+ echo "Manual workflow run - will build all services with Dockerfiles"
39
+ CHANGED_FILES=$(find . -type f) # Consider all files as changed for manual runs
40
+ else
41
+ echo "Detecting changed files since previous commit"
42
+ CHANGED_FILES=$(git diff --name-only HEAD^ HEAD)
43
+ fi
44
+
45
+ # For debugging
46
+ echo "Changed files:"
47
+ echo "$CHANGED_FILES"
48
+
49
+ # Function to check if a service has changes
50
+ function has_changes() {
51
+ local service=$1
52
+ local directory=$2
53
+ # Check for changes in the specific app directory, shared packages, or the workflow file itself
54
+ echo "$CHANGED_FILES" | grep -q "^$directory/$service/" || \
55
+ echo "$CHANGED_FILES" | grep -q "^packages/" || \
56
+ echo "$CHANGED_FILES" | grep -q "^.github/workflows/"
57
+ }
58
+
59
+ # Find all services in 'apps' with Dockerfiles
60
+ APPS_WITH_DOCKERFILE=$(find apps -maxdepth 2 -name "Dockerfile" -type f | sed 's|apps/\([^/]*\)/Dockerfile|\1|')
61
+
62
+ # Filter services with changes
63
+ APPS_SERVICES="[]"
64
+ for APP in $APPS_WITH_DOCKERFILE; do
65
+ if [[ "$IS_MANUAL" == "true" ]] || has_changes "$APP" "apps"; then
66
+ APP_JSON=$(echo "$APP" | jq -R '{"name": ., "directory": "apps"}')
67
+ APPS_SERVICES=$(echo "$APPS_SERVICES" | jq ". + [$APP_JSON]")
68
+ echo "App $APP has changes and will be built"
69
+ else
70
+ echo "App $APP has no changes and will be skipped"
71
+ fi
72
+ done
73
+
74
+ # Output the services to build
75
+ echo "services<<EOF" >> $GITHUB_OUTPUT
76
+ echo "$APPS_SERVICES" >> $GITHUB_OUTPUT
77
+ echo "EOF" >> $GITHUB_OUTPUT
78
+ echo "Services to build: $APPS_SERVICES"
79
+
80
+ version :
81
+ name : Generate Version
82
+ runs-on : ubuntu-latest
83
+ outputs :
84
+ new_version : ${{ steps.set-version.outputs.new_version }}
85
+ steps :
86
+ - name : Checkout code
87
+ uses : actions/checkout@v4
88
+ with :
89
+ fetch-depth : 0 # Fetch all history for version calculation
90
+
91
+ - name : Get current version from Git tags
92
+ id : get-version
93
+ run : |
94
+ # Find the latest tag matching v*.*.*, default to 0.0.0 if none exist
95
+ if git tag -l | grep -q "^v"; then
96
+ LATEST_TAG=$(git tag -l "v*" | sort -V | tail -n 1)
97
+ CURRENT_VERSION=${LATEST_TAG#v}
98
+ echo "current_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
99
+ echo "Current version: $CURRENT_VERSION"
100
+ else
101
+ echo "current_version=0.0.0" >> $GITHUB_OUTPUT
102
+ echo "No version tags found, defaulting to 0.0.0"
103
+ fi
104
+
105
+ - name : Increment patch version
106
+ id : set-version
107
+ run : |
108
+ CURRENT_VERSION=${{ steps.get-version.outputs.current_version }}
109
+ IFS='.' read -ra VERSION_PARTS <<< "$CURRENT_VERSION"
110
+
111
+ MAJOR=${VERSION_PARTS[0]:-0}
112
+ MINOR=${VERSION_PARTS[1]:-0}
113
+ PATCH=${VERSION_PARTS[2]:-0}
114
+
115
+ # Increment patch version number
116
+ NEW_PATCH=$((PATCH + 1))
117
+ NEW_VERSION="$MAJOR.$MINOR.$NEW_PATCH"
118
+
119
+ echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
120
+ echo "New version: $NEW_VERSION"
121
+
122
+ - name : Create and push new Git tag
123
+ run : |
124
+ git config user.name "GitHub Actions"
125
+ git config user.email "[email protected] "
126
+ git tag -a "v${{ steps.set-version.outputs.new_version }}" -m "Release v${{ steps.set-version.outputs.new_version }}"
127
+ git push origin "v${{ steps.set-version.outputs.new_version }}"
128
+
129
+ build-docker-images :
130
+ name : Build Docker Images
131
+ needs : [version, discover-services]
132
+ runs-on : ubuntu-latest
133
+ strategy :
134
+ matrix :
135
+ # Build one image per discovered service
136
+ service : ${{ fromJson(needs.discover-services.outputs.services) }}
137
+ steps :
138
+ - name : Checkout code
139
+ uses : actions/checkout@v4
140
+
141
+ - name : Set up QEMU for multi-platform builds
142
+ uses : docker/setup-qemu-action@v3
143
+
144
+ - name : Set up Docker Buildx
145
+ uses : docker/setup-buildx-action@v3
146
+
147
+ - name : Login to GitHub Container Registry
148
+ uses : docker/login-action@v3
149
+ with :
150
+ registry : ghcr.io
151
+ username : ${{ github.actor }}
152
+ password : ${{ secrets.GITHUB_TOKEN }}
153
+
154
+ - name : Prepare repository name (lowercase)
155
+ id : repo-name
156
+ run : |
157
+ REPO_NAME=$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]')
158
+ echo "lowercased=$REPO_NAME" >> $GITHUB_OUTPUT
159
+
160
+ - name : Build and push multi-platform image
161
+ uses : docker/build-push-action@v5
162
+ with :
163
+ context : .
164
+ # Dynamically set the Dockerfile path based on matrix service info
165
+ file : ./${{ matrix.service.directory }}/${{ matrix.service.name }}/Dockerfile
166
+ push : true
167
+ platforms : linux/amd64,linux/arm64/v8
168
+ tags : |
169
+ ghcr.io/${{ steps.repo-name.outputs.lowercased }}/${{ matrix.service.name }}:${{ needs.version.outputs.new_version }}
170
+ ghcr.io/${{ steps.repo-name.outputs.lowercased }}/${{ matrix.service.name }}:latest
171
+ # Enable build cache for faster builds
172
+ cache-from : type=gha
173
+ cache-to : type=gha,mode=max
174
+
175
+ create-release :
176
+ name : Create GitHub Release
177
+ needs : [version, build-docker-images, discover-services]
178
+ runs-on : ubuntu-latest
179
+ steps :
180
+ - name : Checkout code
181
+ uses : actions/checkout@v4
182
+
183
+ - name : Prepare repository name (lowercase)
184
+ id : repo-name
185
+ run : |
186
+ REPO_NAME=$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]')
187
+ echo "lowercased=$REPO_NAME" >> $GITHUB_OUTPUT
188
+
189
+ - name : Generate image list for release notes
190
+ id : image-list
191
+ run : |
192
+ # Read services JSON from previous job output
193
+ echo '${{ needs.discover-services.outputs.services }}' > services.json
194
+ VERSION=${{ needs.version.outputs.new_version }}
195
+ REPO=${{ steps.repo-name.outputs.lowercased }}
196
+
197
+ IMAGE_LIST=""
198
+ # Loop through service names and create markdown list items
199
+ while read -r SERVICE_NAME; do
200
+ IMAGE_LIST="${IMAGE_LIST}- ghcr.io/${REPO}/${SERVICE_NAME}:${VERSION}\n"
201
+ done < <(jq -r '.[].name' services.json)
202
+
203
+ # Store the list with escaped newlines for use in the release body
204
+ echo "images<<EOF" >> $GITHUB_OUTPUT
205
+ echo -e "$IMAGE_LIST" >> $GITHUB_OUTPUT
206
+ echo "EOF" >> $GITHUB_OUTPUT
207
+
208
+ - name : Create GitHub release with generated notes
209
+ uses : softprops/action-gh-release@v1
210
+ with :
211
+ name : Release v${{ needs.version.outputs.new_version }}
212
+ tag_name : v${{ needs.version.outputs.new_version }}
213
+ generate_release_notes : true # Auto-generate release notes from commits
214
+ body : |
215
+ # Release v${{ needs.version.outputs.new_version }}
216
+
217
+ Docker images built and published:
218
+ ${{ steps.image-list.outputs.images }}
219
+ env :
220
+ GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
0 commit comments