Apps Image automatically monitors upstream repositories and detects when new versions are available. When changes are detected, it creates pull requests with updated meta.json files, which trigger automated Docker image builds.
Overview
The version checking system runs every 2 hours via GitHub Actions and performs these steps:
Scan - Find all applications to check
Clone - Clone or update upstream repositories
Check - Detect version changes using configured strategy
Compare - Compare with current version in meta.json
Update - Create PR with version updates if changes detected
Check Workflow
The main workflow is orchestrated in action/src/check.ts:
async function main () {
// 1. Initialize application manager
const appsManager = new CheckAppsManager ()
// 2. Scan applications
const appPaths = await appsManager . scanApps ()
if ( ! appPaths ?. length ) {
logger . warn ( 'No applications found to process' )
return
}
// 3. Load application contexts
await appsManager . loadApps ( appPaths )
// 4. Execute version checks
const { allApps , outdatedApps } = await appsManager . checkAllVersions ()
if ( ! outdatedApps ?. size ) {
logger . info ( 'All apps are up to date, no updates needed' )
return
}
// 5. Build PR data
const prResults = await appsManager . buildPrDatas ( outdatedApps )
// 6. Create pull requests
await appsManager . createPr ( prResults )
}
Version Check Types
Apps Image supports five version check strategies, each suited for different upstream patterns.
Type: version
Extract version from a file in the upstream repository, typically package.json.
Configuration :
{
"checkver" : {
"type" : "version" ,
"repo" : "https://github.com/srcbookdev/srcbook" ,
"file" : "srcbook/package.json"
}
}
How it works (action/src/variant.ts:248-286):
Clones the upstream repository
Reads the specified file
For package.json, extracts the version field
For other files, uses regex to extract version
Validates as semver and removes v prefix
Gets the commit SHA for the file
Example : srcbook tracks version from srcbook/package.json
// apps/srcbook/meta.json
{
"name" : "srcbook" ,
"variants" : {
"latest" : {
"version" : "0.0.19" ,
"sha" : "0ba24b1ea83b0dd20137236ce9aebc3ff86bff37" ,
"checkver" : {
"type" : "version" ,
"repo" : "https://github.com/srcbookdev/srcbook" ,
"file" : "srcbook/package.json"
}
}
}
}
With Custom Regex :
{
"checkver" : {
"type" : "version" ,
"repo" : "owner/repo" ,
"file" : "VERSION.txt" ,
"regex" : "v([0-9.]+)"
}
}
When tracking package.json, the version field is automatically extracted. For other files, provide a regex with a capture group.
Type: sha
Track the latest commit SHA from a repository or specific path.
Configuration :
{
"checkver" : {
"type" : "sha" ,
"repo" : "https://github.com/antfu-collective/icones"
}
}
How it works (action/src/variant.ts:220-243):
Clones the upstream repository
If targetVersion specified, gets that specific SHA
If path specified, gets latest commit affecting that path
Otherwise, gets latest commit SHA
Returns short SHA (7 chars) as version
Example 1 : Full repository tracking
// apps/icones/meta.json
{
"variants" : {
"latest" : {
"version" : "0bc5918" ,
"sha" : "0bc59182623617a9238023c183a72863c0ebdfca" ,
"checkver" : {
"type" : "sha" ,
"repo" : "https://github.com/antfu-collective/icones"
}
}
}
}
Example 2 : Path-specific tracking (for monorepos)
// apps/cobalt/meta.json (dev variant)
{
"variants" : {
"dev" : {
"version" : "8d9bccc" ,
"sha" : "8d9bccc4fedabb6842fab71bd14e805f1ea21336" ,
"checkver" : {
"type" : "sha" ,
"repo" : "https://github.com/imputnet/cobalt" ,
"path" : "web"
}
}
}
}
Example 3 : Branch tracking
// apps/weserv/meta.json
{
"variants" : {
"latest" : {
"version" : "0f029b4" ,
"sha" : "0f029b475c0ace517205ff88a967449aea0b2c41" ,
"checkver" : {
"type" : "sha" ,
"repo" : "weserv/images" ,
"branch" : "5.x"
}
}
}
}
Use path to track changes in specific directories of monorepos, ensuring builds only trigger when relevant code changes.
Type: tag
Track Git tags, automatically finding the latest valid semver tag.
Configuration :
{
"checkver" : {
"type" : "tag" ,
"repo" : "https://github.com/lsky-org/lsky-pro"
}
}
How it works (action/src/variant.ts:170-215):
Clones the upstream repository
Gets all Git tags
If targetVersion specified, uses that tag
If tagPattern specified, finds first matching tag
Otherwise, finds first valid semver tag
Returns tag (without v prefix) and its commit SHA
Example 1 : Auto semver detection
// apps/lsky/meta.json
{
"variants" : {
"latest" : {
"version" : "2.1" ,
"sha" : "923f567e0a93c7291c4c9fc5279da9847ed39f7e" ,
"checkver" : {
"type" : "tag" ,
"repo" : "https://github.com/lsky-org/lsky-pro"
}
}
}
}
Automatically finds tags like v2.1, 2.1.0, v2.0.1.
Example 2 : Pattern matching
{
"checkver" : {
"type" : "tag" ,
"repo" : "owner/repo" ,
"tagPattern" : "^release-[0-9]+ \\ .[0-9]+ \\ .[0-9]+$"
}
}
Matches tags like release-1.2.3, release-2.0.0.
Example 3 : Target specific tag
{
"checkver" : {
"type" : "tag" ,
"repo" : "owner/repo" ,
"targetVersion" : "v2.0.0"
}
}
When using tagPattern, ensure the regex is properly escaped in JSON. Use double backslashes (\\) for special characters.
Type: manual
No automatic version checking. Versions must be manually updated in meta.json.
Configuration :
{
"checkver" : {
"type" : "manual"
}
}
How it works (action/src/variant.ts:296-317):
Reads previous meta.json from git history
Compares current version with previous version
If version changed, updates SHA to current commit
No external repository cloning
Example : Base images
// base/nginx/meta.json
{
"variants" : {
"latest" : {
"version" : "0.1.0" ,
"sha" : "363f047cd6f9f160cb6bb142afb8d14191aac07e" ,
"checkver" : {
"type" : "manual"
}
}
}
}
Use cases :
Base images that don’t track upstream
Custom-built images
Images requiring manual testing before updates
With manual type, you manually edit the version field in meta.json. The system detects the change and updates the sha automatically.
Type: registry
Future Feature - Track versions from Docker registries.
{
"checkver" : {
"type" : "registry" ,
"registry" : "docker.io" ,
"image" : "library/nginx"
}
}
Registry type is defined in the schema but not yet implemented. It will enable mirroring images from other registries.
Version Detection Logic
The VariantContext class handles version detection (action/src/variant.ts:21-407):
export class VariantContext {
constructor (
public readonly context : string ,
public readonly name : string ,
public readonly variant : ImageVariant ,
) {
// Normalize repository URL
if ( variant ?. checkver ?. repo ) {
variant . checkver . repo = this . detectRepo ( variant . checkver . repo )
}
}
public async check () {
const { version , sha , checkver } = this . variant
// Clone or update repository
const repoPath = await this . git . cloneOrUpdateRepo (
checkver . repo ,
{ branch: checkver . branch , targetVersion: checkver . targetVersion }
)
// Check version by type
const result = await this . checkVersionByType ( repoPath )
// Compare with current version
const needsUpdate = version !== result . version || sha !== result . sha
if ( needsUpdate ) {
// Collect commit info for PR
const commitInfo = await this . git . collectCommitInfo (
repoPath ,
result . sha ,
sha
)
}
return { ... result , needsUpdate , commitInfo }
}
}
Repository Management
The Git class efficiently manages upstream repositories (action/src/git.ts:20-407):
Clone and Cache Strategy
public async cloneOrUpdateRepo ( repoUrl : string , options : CloneOptions ) {
const repoPath = path . join ( this . cacheDir , repoName )
if ( await pathExists ( repoPath )) {
// Repository exists, update it
await this . exec ( 'git fetch --all --tags' , { cwd: repoPath })
if ( options . branch ) {
await this . exec ( `git checkout ${ options . branch } ` , { cwd: repoPath })
await this . exec ( `git pull origin ${ options . branch } ` , { cwd: repoPath })
}
} else {
// Clone new repository
await this . exec ( `git clone ${ repoUrl } ${ repoPath } ` )
}
return repoPath
}
GitHub Actions Cache :
- name : Cache Git repositories
uses : actions/cache@v4
with :
path : .git-cache
key : git-repos-${{ hashFiles('**/meta.json') }}
restore-keys : |
git-repos-
Repositories are cached between runs, reducing clone time and bandwidth.
Error Recovery
The system handles common Git issues:
Detached HEAD Recovery (action/src/git.ts:106-113):
try {
await this . exec ( 'git pull' , { cwd: repoPath })
} catch ( error ) {
logger . debug ( 'Git pull failed, possibly detached HEAD' )
await this . exec ( `rm -rf ${ repoPath } ` )
await this . cloneRepo ( repoUrl , repoPath , options )
}
Shallow to Full Clone (action/src/git.ts:130-140):
public async unshallow ( repoPath : string ) {
try {
const { stdout : isShallow } = await this . exec (
'git rev-parse --is-shallow-repository' ,
{ cwd: repoPath }
)
if ( isShallow === 'true' ) {
await this . exec ( 'git fetch --unshallow' , { cwd: repoPath })
}
} catch ( error ) {
// Already a full clone, ignore error
}
}
Pull Request Creation
When updates are detected, the system creates pull requests with detailed information.
update(apps/icones): update latest version to 0bc5918
For multiple variants:
update(apps/lsky): update latest version to 2.1, update dev version to 38d52c4
PR Body Content
The PR body includes (action/src/context/checkAppContext.ts:83-195):
Summary table with version changes
Commit information from upstream
Changed files list
Metadata about the update
Example PR Body :
## Version Updates
| Variant | Current | New | Status |
|---------|---------|-----|--------|
| latest | 2.0 | 2.1 | ✅ Update available |
| dev | 35a1b2c | 38d52c4 | ✅ Update available |
## Commits (latest)
### 923f567e (2024-03-01)
**Author** : maintainer < [email protected] >
**Message** :
Release version 2.1
Add new feature X
Fix bug Y
Update dependencies
## Changed Files
- `apps/lsky/meta.json`
---
<sub>Auto-generated by Apps Image version checker</sub>
Auto-merge Labels
PRs are automatically labeled:
Application name (e.g., lsky, icones)
automerge - Enables automatic merging if CI passes
// action/src/context/checkAppContext.ts:99
labels : [ this . name , 'automerge' ]
Configure GitHub branch protection to require status checks before auto-merge, ensuring all builds pass.
Scheduling and Triggers
Automated Schedule
Runs every 2 hours:
# .github/workflows/check-version.yaml
on :
schedule :
- cron : '0 */2 * * *' # Every 2 hours
Manual Trigger
Check specific applications on demand:
workflow_dispatch :
inputs :
context :
description : 'Context directory (apps/icones, all, etc.)'
type : choice
options :
- all
- apps/icones
- apps/rayso
# ...
create_pr :
type : choice
default : 'false'
options :
- 'true'
- 'false'
- development
Usage :
# Via GitHub UI: Actions → Check Version → Run workflow
# Select context: apps/icones
# Select create_pr: true
Push Trigger
Runs when meta.json or Dockerfile changes:
on :
push :
branches :
- master
paths :
- apps/*/meta.json
- apps/*/Dockerfile
- apps/*/Dockerfile.*
Skip checks with commit message:
git commit -m "update docs [skip check]"
Advanced Configuration
Check Frequency
Control how often a variant is checked:
{
"checkver" : {
"type" : "sha" ,
"repo" : "owner/repo" ,
"checkFrequency" : "daily"
}
}
Options :
always (default) - Check every run
daily - Check once per day
weekly - Check once per week
manual - Only check on manual trigger
checkFrequency is defined in the schema but not yet fully implemented. Currently, all checks run on every scheduled execution.
Target Version
Pin to a specific version temporarily:
{
"checkver" : {
"type" : "tag" ,
"repo" : "owner/repo" ,
"targetVersion" : "v2.0.0"
}
}
When set:
Version checking stops at this version
Newer versions are ignored
Remove targetVersion to resume normal tracking
Process Files
Define files that need placeholder replacement:
{
"checkver" : {
"type" : "sha" ,
"repo" : "owner/repo" ,
"processFiles" : [
"Dockerfile" ,
"docker-compose.yml" ,
"README.md"
]
}
}
During version updates, these files are scanned for placeholders like {{version}}, {{sha}} and updated accordingly.
Debugging Version Checks
Local Testing with act
Test version checking locally using act :
# Check single application
act workflow_dispatch -W .github/workflows/check-version.yaml \
--input debug= true \
--input context=apps/icones
# Check multiple applications
act workflow_dispatch -W .github/workflows/check-version.yaml \
--input debug= true \
--input context=apps/icones,apps/rayso
# Check all applications
act workflow_dispatch -W .github/workflows/check-version.yaml \
--input debug= true \
--input context=all
Debug Mode
Enable detailed logging:
workflow_dispatch :
inputs :
debug :
type : boolean
default : true
Debug output includes:
Repository clone/update operations
Version detection details
Comparison results
PR payload data
Validation Before Commits
Validate meta.json changes:
# Check if version format is valid
jq '.variants.latest.version' apps/myapp/meta.json
# Verify SHA length (should be 40 characters)
jq -r '.variants.latest.sha | length' apps/myapp/meta.json
# Validate against schema
npx ajv-cli validate -s .vscode/meta.schema.json -d apps/myapp/meta.json
Parallel Checking
All applications are checked in parallel:
// action/src/context/checkAppsManager.ts
public async checkAllVersions () {
const results = await Promise . all (
this . apps . map ( app => app . checkVersions ())
)
// Process results...
}
Cached Repositories
Git caching significantly reduces check time:
First run : ~2-3 minutes (clone all repos)
Subsequent runs : ~30-60 seconds (update cached repos)
Selective Scanning
When checking specific contexts, only those applications are processed:
// action/src/context/checkAppsManager.ts
public async scanApps () {
const { context } = getCheckVersionConfig ()
if ( context && context !== 'all' ) {
// Only scan specified contexts
return context . split ( ',' ). map ( c => c . trim ())
}
// Scan all apps/*, base/*, test/*
return await globby ([ 'apps/*' , 'base/*' , 'test/*' ])
}
Best Practices
Choose the Right Check Type
version : For projects with package.json or VERSION files
sha : For projects without formal releases or rapid iteration
tag : For projects with semantic versioning tags
manual : For base images or when manual control is needed
Use Path Tracking for Monorepos
Avoid unnecessary builds by tracking specific paths: {
"checkver" : {
"type" : "sha" ,
"repo" : "owner/monorepo" ,
"path" : "packages/web"
}
}
When upstream has breaking changes, pin to last working version: {
"checkver" : {
"type" : "tag" ,
"repo" : "owner/repo" ,
"targetVersion" : "v2.5.0"
}
}
Update Dockerfile to handle breaking changes, then remove targetVersion.
Regularly check that PRs are being created and auto-merged:
Review GitHub Actions logs
Check PR labels and merge status
Verify image builds after merges
Troubleshooting
Version Not Detected
Problem : Version check runs but doesn’t detect new version.
Solutions :
Verify repository URL :
git ls-remote https://github.com/owner/repo
Check branch exists :
{
"checkver" : {
"branch" : "main" // Try "master" if "main" fails
}
}
Validate tag pattern :
git ls-remote --tags https://github.com/owner/repo
PR Not Created
Problem : Version detected but no PR created.
Solutions :
Check create_pr setting :
with :
create_pr : 'true' # Must be string 'true'
Verify GitHub token permissions :
Token needs repo and pull_request scopes
Check secrets.PAT is configured
Review logs for PR creation errors
Problem : Wrong version number extracted.
Solutions :
Add custom regex :
{
"checkver" : {
"type" : "version" ,
"file" : "VERSION" ,
"regex" : "version: \\ s*([0-9.]+)"
}
}
Check file path :
{
"file" : "packages/app/package.json" // Include subdirectories
}