diff --git a/.github/workflows/docker-reusable.yml b/.github/workflows/docker-reusable.yml new file mode 100644 index 0000000..1c53f9e --- /dev/null +++ b/.github/workflows/docker-reusable.yml @@ -0,0 +1,181 @@ +name: Docker - Reusable + + +on: + workflow_call: + inputs: + docker_file: + default: docker/Dockerfile + description: Dockerfile + required: false + type: string + docker_repo: + default: codexstorage/codex-marketplace-ui + description: DockerHub repository + required: false + type: string + tag_latest: + default: true + description: Set latest tag for Docker images + required: false + type: boolean + tag_sha: + default: true + description: Set Git short commit as Docker tag + required: false + type: boolean + tag_suffix: + default: '' + description: Suffix for Docker images tag + required: false + type: string + + +env: + DOCKER_FILE: ${{ inputs.docker_file }} + DOCKER_REPO: ${{ inputs.docker_repo }} + TAG_LATEST: ${{ inputs.tag_latest }} + TAG_SHA: ${{ inputs.tag_sha }} + TAG_SUFFIX: ${{ inputs.tag_suffix }} + + +jobs: + # Build platform specific image + build: + strategy: + fail-fast: true + matrix: + target: + - os: linux + arch: amd64 + - os: linux + arch: arm64 + include: + - target: + os: linux + arch: amd64 + builder: ubuntu-22.04 + - target: + os: linux + arch: arm64 + builder: buildjet-4vcpu-ubuntu-2204-arm + + name: Build ${{ matrix.target.os }}/${{ matrix.target.arch }} + runs-on: ${{ matrix.builder }} + env: + PLATFORM: ${{ format('{0}/{1}', 'linux', matrix.target.arch) }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Docker - Meta + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.DOCKER_REPO }} + + - name: Docker - Set up Buildx + uses: docker/setup-buildx-action@v3 + + - name: Docker - Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Docker - Build and Push by digest + id: build + uses: docker/build-push-action@v6 + with: + context: . + file: ${{ env.DOCKER_FILE }} + platforms: ${{ env.PLATFORM }} + push: true + build-args: | + VITE_CODEX_API_URL=${{ secrets.VITE_CODEX_API_URL }} + VITE_GEO_IP_URL=${{ secrets.VITE_GEO_IP_URL }} + labels: ${{ steps.meta.outputs.labels }} + outputs: type=image,name=${{ env.DOCKER_REPO }},push-by-digest=true,name-canonical=true,push=true + + - name: Docker - Export digest + run: | + mkdir -p /tmp/digests + digest="${{ steps.build.outputs.digest }}" + touch "/tmp/digests/${digest#sha256:}" + + - name: Docker - Upload digest + uses: actions/upload-artifact@v4 + with: + name: digests-${{ matrix.target.arch }} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + + + # Publish multi-platform image + publish: + name: Publish multi-platform image + runs-on: ubuntu-latest + needs: build + steps: + - name: Docker - Variables + run: | + # Adjust custom suffix when set and + if [[ -n "${{ env.TAG_SUFFIX }}" ]]; then + echo "TAG_SUFFIX=-${{ env.TAG_SUFFIX }}" >>$GITHUB_ENV + fi + # Disable SHA tags on tagged release + if [[ ${{ startsWith(github.ref, 'refs/tags/') }} == "true" ]]; then + echo "TAG_SHA=false" >>$GITHUB_ENV + fi + # Handle latest and latest-custom using raw + if [[ ${{ env.TAG_SHA }} == "false" ]]; then + echo "TAG_LATEST=false" >>$GITHUB_ENV + echo "TAG_RAW=true" >>$GITHUB_ENV + if [[ -z "${{ env.TAG_SUFFIX }}" ]]; then + echo "TAG_RAW_VALUE=latest" >>$GITHUB_ENV + else + echo "TAG_RAW_VALUE=latest-{{ env.TAG_SUFFIX }}" >>$GITHUB_ENV + fi + else + echo "TAG_RAW=false" >>$GITHUB_ENV + fi + + - name: Docker - Download digests + uses: actions/download-artifact@v4 + with: + pattern: digests-* + merge-multiple: true + path: /tmp/digests + + - name: Docker - Set up Buildx + uses: docker/setup-buildx-action@v3 + + - name: Docker - Meta + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.DOCKER_REPO }} + flavor: | + latest=${{ env.TAG_LATEST }} + suffix=${{ env.TAG_SUFFIX }},onlatest=true + tags: | + type=semver,pattern={{version}} + type=raw,enable=${{ env.TAG_RAW }},value=latest + type=sha,enable=${{ env.TAG_SHA }} + + - name: Docker - Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Docker - Create manifest list and push + working-directory: /tmp/digests + run: | + docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf '${{ env.DOCKER_REPO }}@sha256:%s ' *) + + - name: Docker - Inspect image + run: | + docker buildx imagetools inspect ${{ env.DOCKER_REPO }}:${{ steps.meta.outputs.version }} diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000..0bed160 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,26 @@ +name: Docker + + +on: + push: + branches: + - master + tags: + - 'v*.*.*' + paths-ignore: + - '**/*.md' + - '.gitignore' + - 'docker/**' + - '!docker/Dockerfile' + - '.github/**' + - '!.github/workflows/docker.yml' + - '!.github/workflows/docker-reusable.yml' + - 'LICENSE-*' + workflow_dispatch: + + +jobs: + build-and-push: + name: Build and Push + uses: ./.github/workflows/docker-reusable.yml + secrets: inherit diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..596bbd6 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,38 @@ +# Variables +ARG BUILDER=node:22-alpine +ARG IMAGE=nginx:1.27-alpine-slim +ARG APP_USER=root +ARG BUILD_HOME=/app +ARG BUILD_OUT=dist +ARG APP_HOME=/usr/share/nginx/html +ARG VITE_CODEX_API_URL=${VITE_CODEX_API_URL:-http://127.0.0.1:8080} +ARG VITE_GEO_IP_URL=${VITE_GEO_IP_URL:-http://127.0.0.1:8080} + + +# Build +FROM ${BUILDER} AS builder + +ARG APP_USER +ARG BUILD_HOME +ARG VITE_CODEX_API_URL +ARG VITE_GEO_IP_URL + +WORKDIR ${BUILD_HOME} +COPY --chown=${APP_USER}:${APP_USER} . . + +RUN npm install +RUN npm run build + + +# Create +FROM ${IMAGE} + +ARG APP_USER +ARG BUILD_HOME +ARG BUILD_OUT +ARG APP_HOME + +WORKDIR ${APP_HOME} +COPY --chown=${APP_USER}:${APP_USER} --from=builder ${BUILD_HOME}/${BUILD_OUT} . + +EXPOSE 80 diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 0000000..229abfb --- /dev/null +++ b/docker/README.md @@ -0,0 +1,48 @@ +# Codex Marketplace UI Docker images + +## Description + + We are shipping Codex Marketplace UI as a Docker image as well. + + [Dockerfile](Dockerfile) is using multi-stage build and we use `alpine` image to speed up the build and to minimize the final Docker image size we are using a lightweight Nginx image. + + +## Build locally + + We can build image locally in the following way + 1. [Install Docker](https://docs.docker.com/engine/install) + + 2. Clone repository + ```shell + git clone https://github.com/codex-storage/codex-marketplace-ui + cd codex-marketplace-ui + ``` + + 3. Build the image + ```shell + # Variables + VITE_CODEX_API_URL= + VITE_GEO_IP_URL= + + # Build + docker build \ + --build-arg VITE_CODEX_API_URL=${VITE_CODEX_API_URL} \ + --build-arg VITE_GEO_IP_URL=${VITE_GEO_IP_URL} \ + --no-cache \ + -f docker/Dockerfile \ + -t codex-marketplace-ui:local . + ``` + + +## How to run + + Base [Nginx image](https://hub.docker.com/_/nginx) is [exposing](https://docs.docker.com/reference/dockerfile/#expose) port 80 and we can [publish](https://docs.docker.com/get-started/docker-concepts/running-containers/publishing-ports/) it to a custom local port + ```shell + docker run \ + --rm \ + --name codex-marketplace-ui \ + -p 3000:80 \ + codexstorage/codex-marketplace-ui:latest + ``` + + Access UI on http://localhost:3000.