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
8 changes: 6 additions & 2 deletions .github/actions/load-docker-image/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ inputs:
image-tags:
description: 'Docker image tags (multi-line string)'
required: true
artifact-name:
description: 'Name of the artifact to download (fork PRs only)'
required: false
default: 'docker-image'

runs:
using: 'composite'
Expand All @@ -15,14 +19,14 @@ runs:
if: inputs.is-fork == 'true'
uses: actions/download-artifact@v4
with:
name: docker-image
name: ${{ inputs.artifact-name }}

- name: Load image from artifact (fork PR)
if: inputs.is-fork == 'true'
shell: bash
run: |
echo "Loading Docker image from artifact..."
gunzip -c docker-image.tar.gz | docker load
gunzip -c ${{ inputs.artifact-name }}.tar.gz | docker load
echo "Available images after load:"
docker images
Expand Down
143 changes: 0 additions & 143 deletions .github/scripts/setup.js

This file was deleted.

163 changes: 154 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1062,6 +1062,142 @@ jobs:
image-digest: ${{ steps.build.outputs.digest }}
is-fork: ${{ steps.strategy.outputs.is-fork-pr }}

job_docker_build_production:
name: Build & Push Production Docker Images
needs: [job_setup]
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: true

- uses: actions/setup-node@v4
env:
FORCE_COLOR: 0
with:
node-version: ${{ env.NODE_VERSION }}

- name: Restore caches
uses: ./.github/actions/restore-cache
env:
DEPENDENCY_CACHE_KEY: ${{ needs.job_setup.outputs.dependency_cache_key }}

- name: Build server and admin assets
run: yarn build:production

- name: Pack standalone distribution
run: yarn workspace ghost pack:standalone

- name: Prepare Docker build context
run: mv ghost/core/package/ /tmp/ghost-production/

- name: Determine push strategy
id: strategy
run: |
IS_FORK_PR="false"
if [ "${{ github.event_name }}" = "pull_request" ] && \
[ "${{ github.event.pull_request.head.repo.full_name }}" != "${{ github.repository }}" ]; then
IS_FORK_PR="true"
fi
OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
echo "is-fork-pr=$IS_FORK_PR" >> $GITHUB_OUTPUT
echo "should-push=$( [ "$IS_FORK_PR" = "false" ] && echo "true" || echo "false" )" >> $GITHUB_OUTPUT
echo "owner=$OWNER" >> $GITHUB_OUTPUT

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

- name: Log in to GitHub Container Registry
if: steps.strategy.outputs.should-push == 'true'
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Docker meta (core)
id: meta-core
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ steps.strategy.outputs.owner }}/ghost-core
tags: |
type=ref,event=branch
type=ref,event=pr
type=sha
type=raw,value=latest,enable={{is_default_branch}}
labels: |
org.opencontainers.image.title=Ghost Core
org.opencontainers.image.description=Ghost production build (server only, no admin)
org.opencontainers.image.vendor=TryGhost

- name: Docker meta (full)
id: meta-full
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ steps.strategy.outputs.owner }}/ghost
tags: |
type=ref,event=branch
type=ref,event=pr
type=sha
type=raw,value=latest,enable={{is_default_branch}}
labels: |
org.opencontainers.image.title=Ghost
org.opencontainers.image.description=Ghost production build (server + admin)
org.opencontainers.image.vendor=TryGhost

- name: Build & push core image
uses: docker/build-push-action@v6
with:
context: /tmp/ghost-production
file: Dockerfile.production
target: core
build-args: NODE_VERSION=${{ env.NODE_VERSION }}
push: ${{ steps.strategy.outputs.should-push }}
load: ${{ steps.strategy.outputs.should-push == 'false' }}
tags: ${{ steps.meta-core.outputs.tags }}
labels: ${{ steps.meta-core.outputs.labels }}
cache-from: type=registry,ref=ghcr.io/${{ steps.strategy.outputs.owner }}/ghost-core:cache-main
cache-to: ${{ steps.strategy.outputs.should-push == 'true' && format('type=registry,ref=ghcr.io/{0}/ghost-core:cache-{1},mode=max', steps.strategy.outputs.owner, github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || 'main') || '' }}

- name: Build & push full image
uses: docker/build-push-action@v6
with:
context: /tmp/ghost-production
file: Dockerfile.production
target: full
build-args: NODE_VERSION=${{ env.NODE_VERSION }}
push: ${{ steps.strategy.outputs.should-push }}
load: ${{ steps.strategy.outputs.should-push == 'false' }}
tags: ${{ steps.meta-full.outputs.tags }}
labels: ${{ steps.meta-full.outputs.labels }}
cache-from: type=registry,ref=ghcr.io/${{ steps.strategy.outputs.owner }}/ghost:cache-main
cache-to: ${{ steps.strategy.outputs.should-push == 'true' && format('type=registry,ref=ghcr.io/{0}/ghost:cache-{1},mode=max', steps.strategy.outputs.owner, github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || 'main') || '' }}

- name: Save full image as artifact (fork PR)
if: steps.strategy.outputs.is-fork-pr == 'true'
run: |
IMAGE_TAG=$(echo "${{ steps.meta-full.outputs.tags }}" | head -n1)
echo "Saving image: $IMAGE_TAG"
docker save "$IMAGE_TAG" | gzip > docker-image-production.tar.gz
echo "Image saved as docker-image-production.tar.gz"
ls -lh docker-image-production.tar.gz

- name: Upload image artifact (fork PR)
if: steps.strategy.outputs.is-fork-pr == 'true'
uses: actions/upload-artifact@v4
with:
name: docker-image-production
path: docker-image-production.tar.gz
retention-days: 1

outputs:
image-tags: ${{ steps.meta-full.outputs.tags }}
is-fork: ${{ steps.strategy.outputs.is-fork-pr }}

job_inspect_image:
name: Inspect Docker Image
needs: job_docker_build
Expand Down Expand Up @@ -1132,7 +1268,7 @@ jobs:
job_e2e_tests:
name: E2E Tests (${{ matrix.shardIndex }}/${{ matrix.shardTotal }})
runs-on: ubuntu-latest
needs: [job_docker_build, job_setup]
needs: [job_docker_build_production, job_setup]
strategy:
fail-fast: true
matrix:
Expand Down Expand Up @@ -1160,17 +1296,13 @@ jobs:
uses: ./.github/actions/load-docker-image
id: load
with:
is-fork: ${{ needs.job_docker_build.outputs.is-fork }}
image-tags: ${{ needs.job_docker_build.outputs.image-tags }}
is-fork: ${{ needs.job_docker_build_production.outputs.is-fork }}
image-tags: ${{ needs.job_docker_build_production.outputs.image-tags }}
artifact-name: docker-image-production

- name: Setup Docker Registry Mirrors
uses: ./.github/actions/setup-docker-registry-mirrors

- name: Pull images
env:
GHOST_IMAGE_TAG: ${{ steps.load.outputs.image-tag }}
run: docker compose -f e2e/compose.yml pull

- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
Expand All @@ -1180,12 +1312,25 @@ jobs:
env:
DEPENDENCY_CACHE_KEY: ${{ needs.job_setup.outputs.dependency_cache_key }}

- name: Build public app UMD bundles
run: yarn workspace @tryghost/e2e build:apps

- name: Build E2E image layer
env:
GHOST_E2E_BASE_IMAGE: ${{ steps.load.outputs.image-tag }}
run: yarn workspace @tryghost/e2e build:docker

- name: Pull images
env:
GHOST_E2E_IMAGE: ghost-e2e:local
run: docker compose -f e2e/compose.yml pull

- name: Setup Playwright
uses: ./.github/actions/setup-playwright

- name: Run e2e tests
env:
GHOST_IMAGE_TAG: ${{ steps.load.outputs.image-tag }}
GHOST_E2E_IMAGE: ghost-e2e:local
TEST_WORKERS_COUNT: 1
run: yarn test:e2e:all --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --retries=2

Expand Down
Loading
Loading