Skip to content
Closed
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
168 changes: 168 additions & 0 deletions .github/workflows/publish-gcp-images.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
name: "☁️ Publish Images to GCP"

on:
workflow_call:
inputs:
image_tag:
description: The image tag to publish
type: string
required: false
default: ""

permissions:
contents: read
id-token: write

env:
PROJECT_ID: "basicblock"
GAR_LOCATION: "us-central1"
GAR_REPOSITORY: "docker-images"
WORKLOAD_IDENTITY_PROVIDER: "projects/327281795986/locations/global/workloadIdentityPools/github-pool/providers/basicblock-repo"
SERVICE_ACCOUNT_EMAIL: "githubactions@basicblock.iam.gserviceaccount.com"

jobs:
publish-webapp:
name: "Build and push webapp"
runs-on: ubuntu-latest
env:
PRISMA_ENGINES_CHECKSUM_IGNORE_MISSING: 1
steps:
- name: ⬇️ Checkout repo
uses: actions/checkout@v4
with:
submodules: recursive

- name: "#️⃣ Get image tag"
id: get_tag
uses: ./.github/actions/get-image-tag
with:
tag: ${{ inputs.image_tag }}

- name: 📛 Set tags to push
id: set_tags
run: |
ref_without_tag=${{ env.GAR_LOCATION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/${{ env.GAR_REPOSITORY }}/trigger.dev
image_tags=$ref_without_tag:${{ steps.get_tag.outputs.tag }}

if [[ "${{ steps.get_tag.outputs.is_semver }}" == true ]]; then
image_tags=$image_tags,$ref_without_tag:v4-beta
fi

echo "image_tags=${image_tags}" >> "$GITHUB_OUTPUT"

- name: 📝 Set build info
id: set_build_info
run: |
tag=${{ steps.get_tag.outputs.tag }}
if [[ "${{ steps.get_tag.outputs.is_semver }}" == true ]]; then
echo "BUILD_APP_VERSION=${tag}" >> "$GITHUB_OUTPUT"
fi
echo "BUILD_GIT_SHA=${{ github.sha }}" >> "$GITHUB_OUTPUT"
echo "BUILD_GIT_REF_NAME=${{ github.ref_name }}" >> "$GITHUB_OUTPUT"
echo "BUILD_TIMESTAMP_SECONDS=$(date +%s)" >> "$GITHUB_OUTPUT"

- name: 'Authenticate to Google Cloud'
id: auth
uses: google-github-actions/auth@v2
with:
token_format: access_token
service_account: ${{ env.SERVICE_ACCOUNT_EMAIL }}
workload_identity_provider: ${{ env.WORKLOAD_IDENTITY_PROVIDER }}
project_id: ${{ env.PROJECT_ID }}

- name: 'Docker auth'
uses: docker/login-action@v3
with:
username: oauth2accesstoken
password: ${{ steps.auth.outputs.access_token }}
registry: ${{ env.GAR_LOCATION }}-docker.pkg.dev

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

- name: 🐳 Build image and push to Artifact Registry
uses: docker/build-push-action@v6
with:
context: .
file: ./docker/Dockerfile
platforms: linux/amd64
tags: ${{ steps.set_tags.outputs.image_tags }}
push: true
build-args: |
BUILD_APP_VERSION=${{ steps.set_build_info.outputs.BUILD_APP_VERSION }}
BUILD_GIT_SHA=${{ steps.set_build_info.outputs.BUILD_GIT_SHA }}
BUILD_GIT_REF_NAME=${{ steps.set_build_info.outputs.BUILD_GIT_REF_NAME }}
BUILD_TIMESTAMP_SECONDS=${{ steps.set_build_info.outputs.BUILD_TIMESTAMP_SECONDS }}
SENTRY_RELEASE=${{ steps.set_build_info.outputs.BUILD_GIT_SHA }}
SENTRY_ORG=triggerdev
SENTRY_PROJECT=trigger-cloud
secrets: |
sentry_auth_token=
Comment on lines +99 to +100

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 GCP workflow sentry_auth_token is set to empty string

At line 100, the build secret sentry_auth_token= is set to an empty value. This means Sentry source map uploads won't work for GCP-published images. Compare with the existing publish-webapp.yml which likely passes a real secret. This may be intentional if Sentry integration isn't needed for GCP builds, or it could be a placeholder that was forgotten.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


publish-workers:
name: "Build and push ${{ matrix.package }}"
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
package: [coordinator, docker-provider, kubernetes-provider, supervisor]
steps:
- name: ⬇️ Checkout repo
uses: actions/checkout@v4

- name: "#️⃣ Get image tag"
id: get_tag
uses: ./.github/actions/get-image-tag
with:
tag: ${{ inputs.image_tag }}

- name: 📦 Get image repo
id: get_repository
run: |
if [[ "${{ matrix.package }}" == *-provider ]]; then
provider_type=$(echo "${{ matrix.package }}" | cut -d- -f1)
repo=provider/${provider_type}
else
repo="${{ matrix.package }}"
fi
echo "repo=${repo}" >> "$GITHUB_OUTPUT"

- name: 📛 Set tags to push
id: set_tags
run: |
ref_without_tag=${{ env.GAR_LOCATION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/${{ env.GAR_REPOSITORY }}/${{ steps.get_repository.outputs.repo }}
image_tags=$ref_without_tag:${{ steps.get_tag.outputs.tag }}

if [[ "${{ matrix.package }}" == "supervisor" && "${{ steps.get_tag.outputs.is_semver }}" == true ]]; then
image_tags=$image_tags,$ref_without_tag:v4-beta
fi

echo "image_tags=${image_tags}" >> "$GITHUB_OUTPUT"

- name: 'Authenticate to Google Cloud'
id: auth
uses: google-github-actions/auth@v2
with:
token_format: access_token
service_account: ${{ env.SERVICE_ACCOUNT_EMAIL }}
workload_identity_provider: ${{ env.WORKLOAD_IDENTITY_PROVIDER }}
project_id: ${{ env.PROJECT_ID }}

- name: 'Docker auth'
uses: docker/login-action@v3
with:
username: oauth2accesstoken
password: ${{ steps.auth.outputs.access_token }}
registry: ${{ env.GAR_LOCATION }}-docker.pkg.dev

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

- name: 🐳 Build image and push to Artifact Registry
uses: docker/build-push-action@v6
with:
context: .
file: ./apps/${{ matrix.package }}/Containerfile
platforms: linux/amd64
tags: ${{ steps.set_tags.outputs.image_tags }}
push: true
8 changes: 8 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ on:
- ".github/workflows/e2e.yml"
- ".github/workflows/publish-webapp.yml"
- ".github/workflows/publish-worker.yml"
- ".github/workflows/publish-gcp-images.yml"
- "packages/**"
- "!packages/**/*.md"
- "!packages/**/*.eslintrc"
Expand Down Expand Up @@ -76,3 +77,10 @@ jobs:
secrets: inherit
with:
image_tag: ${{ inputs.image_tag }}

publish-gcp-images:
needs: [typecheck, units]
uses: ./.github/workflows/publish-gcp-images.yml
secrets: inherit
with:
image_tag: ${{ inputs.image_tag }}
4 changes: 4 additions & 0 deletions packages/cli-v3/src/build/extensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,10 @@ function applyLayerToManifest(layer: BuildLayer, manifest: BuildManifest): Build
$manifest.image.pkgs = $manifest.image.pkgs.concat(layer.image.pkgs);
$manifest.image.pkgs = Array.from(new Set($manifest.image.pkgs));
}

if (layer.image.entrypointPrefix) {
$manifest.image.entrypointPrefix = [...layer.image.entrypointPrefix];
}
Comment on lines +212 to +214

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 entrypointPrefix replaces rather than concatenates, unlike other image layer properties

In applyLayerToManifest, when a layer has image.entrypointPrefix, the manifest's existing entrypointPrefix is completely replaced ($manifest.image.entrypointPrefix = [...layer.image.entrypointPrefix]). This contrasts with image.pkgs (line 207-209) and image.instructions (line 203-204), which both concatenate across layers.

If two different build extensions each call context.addLayer() with an entrypointPrefix, the second silently overwrites the first. This may be intentional (you'd want exactly one entrypoint prefix, not a merged one), but it's an inconsistency worth documenting. A user with two extensions that both set entrypointPrefix would get surprising behavior where only the last-processed extension's prefix takes effect.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

}

if (layer.conditions) {
Expand Down
31 changes: 25 additions & 6 deletions packages/cli-v3/src/deploy/buildImage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -727,14 +727,26 @@ const parseGenerateOptions = (options: GenerateContainerfileOptions) => {
baseInstructions,
buildArgs,
buildEnvVars,
entrypointPrefix: options.image?.entrypointPrefix ?? [],
packages,
postInstallCommands,
};
};

function serializeEntrypoint(entrypointPrefix: string[], entrypoint: string) {
return JSON.stringify(["dumb-init", ...entrypointPrefix, "node", entrypoint]);
}
Comment on lines +736 to +738

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 entrypointPrefix items are passed as arguments to dumb-init, affecting process execution chain

The serialized entrypoint ["dumb-init", ...entrypointPrefix, "node", entrypoint] means dumb-init will execute the first element of entrypointPrefix as the child process (with remaining items as its arguments), rather than executing node directly. For example, ["dumb-init", "my-wrapper", "node", "entry.js"] makes dumb-init run my-wrapper node entry.js. This is likely the intended use case (wrapping the node process with a custom tool), but users need to understand that their prefix command must pass through to node — it's not just prepending flags. The naming "entrypointPrefix" could lead users to think they're adding flags to dumb-init or node, when they're actually inserting an intermediate command in the exec chain.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


async function generateBunContainerfile(options: GenerateContainerfileOptions) {
const { baseImage, buildArgs, buildEnvVars, postInstallCommands, baseInstructions, packages } =
parseGenerateOptions(options);
const {
baseImage,
buildArgs,
buildEnvVars,
entrypointPrefix,
postInstallCommands,
baseInstructions,
packages,
} = parseGenerateOptions(options);

return `# syntax=docker/dockerfile:1
# check=skip=SecretsUsedInArgOrEnv
Expand Down Expand Up @@ -829,14 +841,21 @@ COPY --from=build --chown=bun:bun /app ./
# Copy the index.json file from the indexer stage
COPY --from=indexer --chown=bun:bun /app/index.json ./

ENTRYPOINT [ "dumb-init", "node", "${options.entrypoint}" ]
ENTRYPOINT ${serializeEntrypoint(entrypointPrefix, options.entrypoint)}
CMD []
`;
}

async function generateNodeContainerfile(options: GenerateContainerfileOptions) {
const { baseImage, buildArgs, buildEnvVars, postInstallCommands, baseInstructions, packages } =
parseGenerateOptions(options);
const {
baseImage,
buildArgs,
buildEnvVars,
entrypointPrefix,
postInstallCommands,
baseInstructions,
packages,
} = parseGenerateOptions(options);

return `# syntax=docker/dockerfile:1
# check=skip=SecretsUsedInArgOrEnv
Expand Down Expand Up @@ -939,7 +958,7 @@ COPY --from=build --chown=node:node /app ./
# Copy the index.json file from the indexer stage
COPY --from=indexer --chown=node:node /app/index.json ./

ENTRYPOINT [ "dumb-init", "node", "${options.entrypoint}" ]
ENTRYPOINT ${serializeEntrypoint(entrypointPrefix, options.entrypoint)}
CMD []
`;
}
Expand Down
3 changes: 0 additions & 3 deletions packages/cli-v3/src/entryPoints/managed-run-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ import { env as stdEnv } from "std-env";
import { readJSONFile } from "../utilities/fileSystem.js";
import { WorkerManifest } from "@trigger.dev/core/v3";
import { ManagedRunController } from "./managed/controller.js";
import { logger } from "../utilities/logger.js";

logger.loggerLevel = "debug";

const manifest = await readJSONFile("./index.json");
const workerManifest = WorkerManifest.parse(manifest);
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/v3/build/extensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export interface BuildLayer {
image?: {
pkgs?: string[];
instructions?: string[];
entrypointPrefix?: string[];
};
build?: {
env?: Record<string, string | undefined>;
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/v3/schemas/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export const BuildManifest = z.object({
.object({
pkgs: z.array(z.string()).optional(),
instructions: z.array(z.string()).optional(),
entrypointPrefix: z.array(z.string()).optional(),
})
.optional(),
otelImportHook: z
Expand Down