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
222 changes: 222 additions & 0 deletions .github/workflows/base-release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
name: Build & Push Base Docker Image

on:
workflow_dispatch:
inputs:
version:
description: 'Version tag for the base image (e.g., v1.0.0)'
required: true
type: string
push_latest:
description: 'Also tag as latest'
required: false
type: boolean
default: false

jobs:
prepare-metadata:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.meta.outputs.version }}
major: ${{ steps.meta.outputs.major }}
major_minor: ${{ steps.meta.outputs.major_minor }}
tags_suffix: ${{ steps.tags.outputs.tags_suffix }}
steps:
- name: Validate version format
run: |
VERSION="${{ github.event.inputs.version }}"
if ! [[ "$VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Error: Version must be in format v1.2.3"
exit 1
fi

- name: Extract version metadata
id: meta
run: |
VERSION="${{ github.event.inputs.version }}"
echo "version=$VERSION" >> $GITHUB_OUTPUT

# Extract major and major.minor versions
if [[ "$VERSION" =~ ^v([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
MAJOR="v${BASH_REMATCH[1]}"
MINOR="${BASH_REMATCH[2]}"
MAJOR_MINOR="v${BASH_REMATCH[1]}.${MINOR}"
echo "major=$MAJOR" >> $GITHUB_OUTPUT
echo "major_minor=$MAJOR_MINOR" >> $GITHUB_OUTPUT
fi

- name: Determine Docker tags
id: tags
run: |
# Start with the full version tag
TAGS_SUFFIX="${{ steps.meta.outputs.version }}"

# Add major and major.minor tags
TAGS_SUFFIX="$TAGS_SUFFIX,${{ steps.meta.outputs.major }}"
TAGS_SUFFIX="$TAGS_SUFFIX,${{ steps.meta.outputs.major_minor }}"

# Optionally add latest tag
if [[ "${{ github.event.inputs.push_latest }}" == "true" ]]; then
TAGS_SUFFIX="$TAGS_SUFFIX,latest"
fi

echo "tags_suffix=$TAGS_SUFFIX" >> $GITHUB_OUTPUT

build-amd64:
runs-on: ubuntu-latest
needs: prepare-metadata
steps:
- name: Check out repository
uses: actions/checkout@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
buildkitd-flags: --debug

- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Build and push AMD64 image
id: build
uses: docker/build-push-action@v4
with:
context: .
file: base.Dockerfile
platforms: linux/amd64
outputs: type=image,name=lifecycleoss/app-base,push-by-digest=true,name-canonical=true,push=true
cache-from: type=registry,ref=lifecycleoss/app-base:buildcache-amd64
cache-to: type=registry,ref=lifecycleoss/app-base:buildcache-amd64,mode=max

- name: Export digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"

- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: digests-amd64
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1

build-arm64:
runs-on: ubuntu-latest
needs: prepare-metadata
steps:
- name: Check out repository
uses: actions/checkout@v3

- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
buildkitd-flags: --debug

- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Build and push ARM64 image
id: build
uses: docker/build-push-action@v4
with:
context: .
file: base.Dockerfile
platforms: linux/arm64
outputs: type=image,name=lifecycleoss/app-base,push-by-digest=true,name-canonical=true,push=true
cache-from: type=registry,ref=lifecycleoss/app-base:buildcache-arm64
cache-to: type=registry,ref=lifecycleoss/app-base:buildcache-arm64,mode=max

- name: Export digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"

- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: digests-arm64
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1

merge-and-push:
runs-on: ubuntu-latest
needs:
- prepare-metadata
- build-amd64
- build-arm64
steps:
- name: Download digests
uses: actions/download-artifact@v4
with:
path: /tmp/digests

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Create manifest list and push
env:
TAGS_SUFFIX: ${{ needs.prepare-metadata.outputs.tags_suffix }}
run: |
# Find digest files
AMD64_DIGEST=$(find /tmp/digests/digests-amd64 -type f | head -1)
ARM64_DIGEST=$(find /tmp/digests/digests-arm64 -type f | head -1)

# Get the digest values (filenames)
AMD64_SHA=$(basename "$AMD64_DIGEST")
ARM64_SHA=$(basename "$ARM64_DIGEST")

# Build the digest list
DIGESTS="lifecycleoss/app-base@sha256:$AMD64_SHA lifecycleoss/app-base@sha256:$ARM64_SHA"

# Convert comma-separated tag suffixes to array
IFS=',' read -ra TAG_ARRAY <<< "$TAGS_SUFFIX"

# Create and push manifest for each tag
for TAG_SUFFIX in "${TAG_ARRAY[@]}"; do
TAG_SUFFIX=$(echo "$TAG_SUFFIX" | xargs) # Trim whitespace
FULL_TAG="lifecycleoss/app-base:${TAG_SUFFIX}"
docker buildx imagetools create -t "$FULL_TAG" $DIGESTS
done

- name: Inspect final image
run: |
docker buildx imagetools inspect lifecycleoss/app-base:${{ needs.prepare-metadata.outputs.version }}

- name: Create GitHub Release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: base-${{ needs.prepare-metadata.outputs.version }}
release_name: Base Image ${{ needs.prepare-metadata.outputs.version }}
body: |
Base image release for lifecycle application.

Docker Hub: `lifecycleoss/app-base:${{ needs.prepare-metadata.outputs.version }}`

Tags created:
- `lifecycleoss/app-base:${{ needs.prepare-metadata.outputs.version }}`
- `lifecycleoss/app-base:${{ needs.prepare-metadata.outputs.major }}`
- `lifecycleoss/app-base:${{ needs.prepare-metadata.outputs.major_minor }}`
${{ github.event.inputs.push_latest == 'true' && '- `lifecycleoss/app-base:latest`' || '' }}
draft: false
prerelease: false
12 changes: 6 additions & 6 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ jobs:
uses: docker/build-push-action@v4
with:
context: .
file: Dockerfile
file: app.Dockerfile
platforms: linux/amd64
outputs: type=image,name=${{ secrets.DOCKERHUB_USERNAME }}/app,push-by-digest=true,name-canonical=true,push=true
build-args: |
Expand Down Expand Up @@ -128,7 +128,7 @@ jobs:
uses: docker/build-push-action@v4
with:
context: .
file: Dockerfile
file: app.Dockerfile
platforms: linux/arm64
outputs: type=image,name=${{ secrets.DOCKERHUB_USERNAME }}/app,push-by-digest=true,name-canonical=true,push=true
build-args: |
Expand Down Expand Up @@ -178,17 +178,17 @@ jobs:
# Find digest files
AMD64_DIGEST=$(find /tmp/digests/digests-amd64 -type f | head -1)
ARM64_DIGEST=$(find /tmp/digests/digests-arm64 -type f | head -1)

# Get the digest values (filenames)
AMD64_SHA=$(basename "$AMD64_DIGEST")
ARM64_SHA=$(basename "$ARM64_DIGEST")

# Build the digest list
DIGESTS="${DOCKER_USERNAME}/app@sha256:$AMD64_SHA ${DOCKER_USERNAME}/app@sha256:$ARM64_SHA"

# Convert comma-separated tag suffixes to array
IFS=',' read -ra TAG_ARRAY <<< "$TAGS_SUFFIX"

# Create and push manifest for each tag
for TAG_SUFFIX in "${TAG_ARRAY[@]}"; do
TAG_SUFFIX=$(echo "$TAG_SUFFIX" | xargs) # Trim whitespace
Expand Down
34 changes: 34 additions & 0 deletions app.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Copyright 2025 GoodRx, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

ARG BASE_IMAGE_TAG=v1
FROM lifecycleoss/app-base:${BASE_IMAGE_TAG} AS packages

ARG PORT

ENV PORT=$PORT

WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN pnpm install

FROM packages

COPY . .

RUN pnpm run build

EXPOSE $PORT

ENTRYPOINT [ "./scripts/k8-start.sh" ]
63 changes: 63 additions & 0 deletions base.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Copyright 2025 GoodRx, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

FROM node:20-slim

ARG TARGETARCH

RUN apt-get update && apt-get install -y \
wget \
unzip \
curl \
jq \
git \
procps \
postgresql-client \
net-tools \
build-essential \
python3 \
ruby \
&& rm -rf /var/lib/apt/lists/*

RUN curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 && \
chmod 700 get_helm.sh && \
./get_helm.sh && \
rm get_helm.sh

RUN if [ "$TARGETARCH" = "arm64" ]; then \
curl "https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip" -o "awscliv2.zip" && \
unzip awscliv2.zip && \
./aws/install && \
rm -rf awscliv2.zip aws/ && \
curl -LO https://dl.k8s.io/release/v1.30.0/bin/linux/arm64/kubectl; \
else \
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" && \
unzip awscliv2.zip && \
./aws/install && \
rm -rf awscliv2.zip aws/ && \
curl -LO https://dl.k8s.io/release/v1.30.0/bin/linux/amd64/kubectl; \
fi && \
chmod +x ./kubectl && \
mv ./kubectl /usr/local/bin/kubectl

RUN npm install pnpm --global

RUN npm install codefresh@0.81.5 --global

RUN npm install dotenv-cli --global

ENV BUILD_MODE=yes
ENV DATABASE_URL=no-db

WORKDIR /app