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
222 changes: 158 additions & 64 deletions .github/workflows/docker-release.yml
Original file line number Diff line number Diff line change
@@ -1,91 +1,185 @@
name: Build and Push emoncms Docker Image

on:
push:
branches: [master]
pull_request:
workflow_dispatch:
inputs:
php_version:
description: 'php_version'
description: php_version
required: true
type: string
default: '8.4'
default: "8.4"
emoncms_src:
description: 'emoncms_src'
description: emoncms_src
required: true
type: string
default: 'emoncms/emoncms'
default: emoncms/emoncms
branch:
description: 'branch'
description: branch
required: true
type: string
default: 'stable'
default: stable

env:
PHP_VERSION: ${{ inputs.php_version || '8.4' }}
EMONCMS_SRC: ${{ inputs.emoncms_src || 'emoncms/emoncms' }}
EMONCMS_BRANCH: ${{ inputs.branch || 'stable' }}
# Debian release of the php:<version>-apache-<suite> base image
DEBIAN_SUITE: trixie

# Publishing runs queue so an older build cannot overwrite a newer :latest.
# Superseded pull request builds are cancelled.
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || 'publish' }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
metadata:
runs-on: ubuntu-latest
outputs:
dockerhub_available: ${{ steps.registry.outputs.available }}
php_image: ${{ steps.php.outputs.image }}
tags: ${{ steps.tags.outputs.tags }}
steps:
- name: Checkout code
uses: actions/checkout@v7

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

- name: Resolve PHP image
id: php
shell: bash
run: |
set -euo pipefail
tag="php:${PHP_VERSION}-apache-${DEBIAN_SUITE}"
digest=$(docker buildx imagetools inspect "$tag" --format '{{.Manifest.Digest}}')
case "$digest" in
sha256:*) ;;
*) printf 'Invalid PHP image digest: %s\n' "$digest" >&2; exit 1 ;;
esac
echo "image=${tag}@${digest}" >> "$GITHUB_OUTPUT"

- name: Check for Docker Hub credentials
id: registry
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
run: |
if [ -n "$DOCKER_USERNAME" ] && [ -n "$DOCKER_PASSWORD" ]; then
echo "available=true" >> "$GITHUB_OUTPUT"
else
echo "available=false" >> "$GITHUB_OUTPUT"
fi

- name: Get emoncms version
id: emoncms_version
run: |
set -euo pipefail
url="https://raw.githubusercontent.com/${EMONCMS_SRC}/${EMONCMS_BRANCH}/version.json"
version=$(curl -fsSL "$url" | jq --raw-output '.version')
echo "$version"
echo "version=$version" >> "$GITHUB_OUTPUT"

- name: Set image tags
id: tags
run: |
VERSION="${{ steps.emoncms_version.outputs.version }}"
if [ "${{ github.event_name }}" = "pull_request" ]; then
TAGS="ghcr.io/${{ github.repository_owner }}/emoncms:pr-${{ github.event.number }}"
elif [ "${{ steps.registry.outputs.available }}" = "true" ]; then
TAGS="openenergymonitor/emoncms:latest,openenergymonitor/emoncms:${VERSION}"
else
TAGS="ghcr.io/${{ github.repository_owner }}/emoncms:latest,ghcr.io/${{ github.repository_owner }}/emoncms:${VERSION}"
fi
echo "tags=$TAGS" >> "$GITHUB_OUTPUT"

extensions:
needs: metadata
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
architecture: [amd64, arm64]
steps:
- name: Checkout code
uses: actions/checkout@v7

- name: Build PHP extensions
run: |
ci/build-extension-artifacts.sh \
"${{ needs.metadata.outputs.php_image }}" \
"${{ matrix.architecture }}" \
"$RUNNER_TEMP/extensions"

- name: Upload PHP extensions
uses: actions/upload-artifact@v7
with:
name: php-extensions-${{ matrix.architecture }}
path: ${{ runner.temp }}/extensions/
if-no-files-found: error
retention-days: 7

build-and-push:
needs: [metadata, extensions]
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Check for Docker Hub credentials
id: check_dockerhub
run: |
if [ -n "${{ secrets.DOCKER_USERNAME }}" ] && [ -n "${{ secrets.DOCKER_PASSWORD }}" ]; then
echo "available=true" >> "$GITHUB_OUTPUT"
else
echo "available=false" >> "$GITHUB_OUTPUT"
fi

- name: Log in to Docker Hub
if: steps.check_dockerhub.outputs.available == 'true' && github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

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

- name: Get emoncms version
id: emoncms_version
run: |
wget https://raw.githubusercontent.com/${{ env.EMONCMS_SRC }}/${{ env.EMONCMS_BRANCH }}/version.json
version=$(cat version.json | jq --raw-output '.version')
echo $version
echo "version=$version" >> "$GITHUB_OUTPUT"

- name: Set image tags
id: tags
run: |
VERSION="${{ steps.emoncms_version.outputs.version }}"
if [ "${{ github.event_name }}" = "pull_request" ]; then
TAGS="ghcr.io/${{ github.repository_owner }}/emoncms:pr-${{ github.event.number }}"
elif [ "${{ steps.check_dockerhub.outputs.available }}" = "true" ]; then
TAGS="openenergymonitor/emoncms:latest,openenergymonitor/emoncms:${VERSION}"
else
TAGS="ghcr.io/${{ github.repository_owner }}/emoncms:latest,ghcr.io/${{ github.repository_owner }}/emoncms:${VERSION}"
fi
echo "tags=$TAGS" >> "$GITHUB_OUTPUT"

- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: ./web
build-args: |
"BUILD_FROM=php:${{ env.PHP_VERSION }}-apache"
"EMONCMS_SRC=https://github.com/${{ env.EMONCMS_SRC }}"
"BRANCH=${{ env.EMONCMS_BRANCH }}"
push: true
tags: ${{ steps.tags.outputs.tags }}
- name: Checkout code
uses: actions/checkout@v7

- name: Download amd64 PHP extensions
uses: actions/download-artifact@v8
with:
name: php-extensions-amd64
path: web/extensions/linux-amd64

- name: Download arm64 PHP extensions
uses: actions/download-artifact@v8
with:
name: php-extensions-arm64
path: web/extensions/linux-arm64

- name: Check extension artifacts
run: |
set -euo pipefail
test "$(find web/extensions -name '*.so' -type f | wc -l)" -eq 8

- name: Set up QEMU
uses: docker/setup-qemu-action@v4
with:
platforms: arm64

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

- name: Log in to Docker Hub
if: needs.metadata.outputs.dockerhub_available == 'true' && github.event_name != 'pull_request'
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

- name: Log in to GitHub Container Registry
if: needs.metadata.outputs.dockerhub_available != 'true' && github.event_name != 'pull_request'
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push Docker image
uses: docker/build-push-action@v7
with:
context: ./web
platforms: linux/amd64,linux/arm64
build-args: |
BUILD_FROM=${{ needs.metadata.outputs.php_image }}
EMONCMS_SRC=https://github.com/${{ env.EMONCMS_SRC }}
BRANCH=${{ env.EMONCMS_BRANCH }}
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ needs.metadata.outputs.tags }}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
emoncms/
.docker-env
*~
web/extensions/
42 changes: 25 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ emonHub docker: https://hub.docker.com/r/alexjunk/emonhub

**Note: This docker installation is not quite a complete Emoncms installation.** In general we recommend building an Emoncms installation using our EmonScripts installation script on a Debian/Ubuntu/RaspberryPi based system, this said this docker image does provide a useful alternative approach to get a simple but functional emoncms installation up and running.

Latest image hosted on docker hub: [openenergymonitor/emoncms:latest](https://hub.docker.com/r/openenergymonitor/emoncms/)
Latest image hosted on docker hub: [openenergymonitor/emoncms:latest](https://hub.docker.com/r/openenergymonitor/emoncms/) (`linux/amd64` and `linux/arm64`)

## Quickstart

Expand Down Expand Up @@ -116,7 +116,16 @@ Edit `config/php.ini` to add custom php settings e.g. timezone (default Europe)

#### Build / update Docker container

Required on first run or if `Dockerfile` or `Docker-compose.yml` are changed:
Required on first run or if `Dockerfile` or `Docker-compose.yml` are changed.

The PHP extensions are not compiled inside the image. Build them first for your
architecture (`amd64` or `arm64`), from the repository root:

```bash
ci/build-extension-artifacts.sh php:8.4-apache-trixie amd64 web/extensions/linux-amd64
```

See [web/README.md](web/README.md) for details. Then:

```bash
docker-compose build
Expand Down Expand Up @@ -272,21 +281,20 @@ docker exec -it emoncms-docker_web_1 /bin/bash

****

## Pushing to docker hub
## Publishing images

From: https://docs.docker.com/docker-hub/repos/
Images are built and published by the
[Build and Push emoncms Docker Image](.github/workflows/docker-release.yml) workflow:

```bash
docker login --username=yourhubusername --email=youremail@company.com
docker tag openenergymonitor/emoncms:<tag-name>
docker push openenergymonitor/emoncms:<tag-name>
```

Tag name should be the Emoncms version e.g 10.x.x

Also push the latest version using `latest` tag
- **Pull requests** build both platforms (`linux/amd64`, `linux/arm64`) but do not push.
- **Pushes to `master`** build and publish the image using the defaults (PHP 8.4, the
`stable` branch of `emoncms/emoncms`).
- **Manual runs** from the Actions tab (`workflow_dispatch`) also publish, and let you
choose the PHP version, emoncms repository and branch.
- If the `DOCKER_USERNAME` and `DOCKER_PASSWORD` secrets are set, the workflow pushes
`openenergymonitor/emoncms:latest` and `openenergymonitor/emoncms:<emoncms version>` to
Docker Hub. Otherwise, for example on a fork, it pushes the same tags to
`ghcr.io/<owner>/emoncms`.

```bash
docker tag openenergymonitor/emoncms:latest
docker tag openenergymonitor/emoncms:latest
```
The PHP extensions are cross-compiled per architecture in a separate job, so the arm64
build does not run a compiler under emulation.
43 changes: 43 additions & 0 deletions ci/build-extension-artifacts.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/bin/bash
set -euo pipefail

image=${1:?usage: build-extension-artifacts.sh <php-image> <amd64|arm64> <output>}
architecture=${2:?missing architecture}
output=${3:?missing output directory}
root=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
sdk=$(mktemp -d)
trap 'rm -rf "$sdk"' EXIT

case "$architecture" in
amd64)
platform=linux/amd64
packages="gcc g++ libc6-dev libmosquitto-dev"
;;
arm64)
platform=linux/arm64
packages="gcc-aarch64-linux-gnu g++-aarch64-linux-gnu libc6-dev:arm64 libmosquitto-dev:arm64"
;;
*)
printf 'Unsupported architecture: %s\n' "$architecture" >&2
exit 2
;;
esac

"$root/ci/extract-php-sdk.sh" "$image" "$platform" "$sdk"
suite=$(sh -c '. "$1"; printf "%s" "$VERSION_CODENAME"' sh "$sdk/rootfs/etc/os-release")
mkdir -p "$output"

docker run --rm --platform linux/amd64 \
-v "$root:/workspace" \
-v "$sdk/rootfs:/php-root:ro" \
-v "$output:/out" \
-w /workspace \
"debian:$suite" \
bash -euxo pipefail -c "
dpkg --add-architecture arm64
apt-get update
apt-get install -y --no-install-recommends \\
autoconf automake binutils ca-certificates file git libtool make pkg-config xz-utils \\
$packages
./ci/build-php-extensions.sh '$architecture' /php-root /out
"
Loading
Loading