This repository uses automated workflows for versioning, releasing, and building Docker images.
The release.yml workflow automates the entire release process using changesets.
-
On every push to
main: The workflow runs and uses the changesets/action to:- Create or update a "Version Packages" pull request that bundles all pending changesets
- Version process: Runs
changeset versionto update package.json files, then runspnpm install --no-frozen-lockfileto update pnpm-lock.yaml with new dependency versions - When that PR is merged, it publishes packages and creates git tags
-
Docker Image Building: After packages are published (when the release PR is merged):
- The workflow automatically detects which packages were versioned
- For each published package, it checks if the package has a
dockerTargetfield in itspackage.json - If
dockerTargetis present, it builds and pushes a Docker image to GitHub Container Registry - Images are tagged with semantic versioning (major, major.minor, full version, and latest)
- ✅ No Docker builds on PR creation: Docker images are only built after packages are actually published
- ✅ Selective building: Only packages that received version updates are processed
- ✅ Auto-discovery: New packages with
dockerTargetfield are automatically discovered and built - ✅ Efficient caching: Uses GitHub Actions cache (
type=gha) for Docker builds
To make a package buildable as a Docker image:
- Add the
dockerTargetfield to the package'spackage.json:
{
"name": "@marshant/my-service",
"version": "0.1.0",
"private": true,
"dockerTarget": "my-service",
"scripts": {
"build": "..."
}
}- Add corresponding stages to the root
Dockerfile:
# Build stage for your service
FROM base AS build-my-service
COPY . /usr/src/app
WORKDIR /usr/src/app
RUN --mount=type=cache,id=pnpm,target=/pnpm/store \
pnpm install -w -F @marshant/my-service... --frozen-lockfile && \
pnpm exec turbo @marshant/my-service#build
# Runtime stage for your service
FROM node:22.19-alpine3.22 AS my-service
WORKDIR /prod/my-service
ENV NODE_ENV=production
COPY --from=build-my-service /usr/src/app/services/my-service/dist ./
CMD ["node", "index.js"]- The workflow will automatically:
- Detect the package when it's versioned
- Build the Docker image using the specified target
- Push to
ghcr.io/<repo>/<package-name>with appropriate tags
Images are published to GitHub Container Registry with the following naming convention:
ghcr.io/<github-username>/<repo-name>/<package-short-name>:<version>
Each image receives multiple tags:
<major>.<minor>.<patch>- Full semantic version<major>.<minor>- Major and minor version<major>- Major version onlylatest- Always points to the most recent version
For @marshant/web version 1.2.3:
ghcr.io/mark-omarov/marshant-feature-flag/web:1.2.3ghcr.io/mark-omarov/marshant-feature-flag/web:1.2ghcr.io/mark-omarov/marshant-feature-flag/web:1ghcr.io/mark-omarov/marshant-feature-flag/web:latest
The workflow works with private packages (those with "private": true). Private packages are:
- Still versioned by changesets
- Can have Docker images built and published
- Won't be published to npm (as intended)
This allows you to version internal services and libraries while only publishing selected packages (like SDKs) to npm in the future.
Check that:
- The package has a
dockerTargetfield inpackage.json - The Dockerfile has a target matching the
dockerTargetvalue - The package was actually versioned in the release (check the changesets output)
- Check the Dockerfile syntax for the specific target
- Ensure all dependencies are properly installed in the build stage
- Verify the build command succeeds locally