diff --git a/.dockerignore b/.dockerignore index 8d20d66d6576..bfdb9d241b65 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,6 +5,7 @@ dist/** build/** build_docs/** *Dockerfile +**/*Dockerfile blueprints/local .git !gen-ts-api/node_modules diff --git a/.github/workflows/ci-website.yml b/.github/workflows/ci-website.yml index 7f565f1ff01c..a3303b4fb0ae 100644 --- a/.github/workflows/ci-website.yml +++ b/.github/workflows/ci-website.yml @@ -41,32 +41,60 @@ jobs: - name: test working-directory: website/ run: npm test - build: + build-container: runs-on: ubuntu-latest - name: ${{ matrix.job }} - strategy: - fail-fast: false - matrix: - job: - - build + permissions: + # Needed to upload container images to ghcr.io + packages: write + # Needed for attestation + id-token: write + attestations: write steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 with: - node-version-file: website/package.json - cache: "npm" - cache-dependency-path: website/package-lock.json - - working-directory: website/ - run: npm ci - - name: build - working-directory: website/ - run: npm run ${{ matrix.job }} + ref: ${{ github.event.pull_request.head.sha }} + - name: Set up QEMU + uses: docker/setup-qemu-action@v3.6.0 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: prepare variables + uses: ./.github/actions/docker-push-variables + id: ev + env: + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + with: + image-name: ghcr.io/goauthentik/dev-docs + - name: Login to Container Registry + if: ${{ steps.ev.outputs.shouldPush == 'true' }} + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Build Docker Image + id: push + uses: docker/build-push-action@v6 + with: + tags: ${{ steps.ev.outputs.imageTags }} + file: website/Dockerfile + push: ${{ steps.ev.outputs.shouldPush == 'true' }} + platforms: linux/amd64,linux/arm64 + context: . + cache-from: type=registry,ref=ghcr.io/goauthentik/dev-docs:buildcache + cache-to: ${{ steps.ev.outputs.shouldPush == 'true' && 'type=registry,ref=ghcr.io/goauthentik/dev-docs:buildcache,mode=max' || '' }} + - uses: actions/attest-build-provenance@v2 + id: attest + if: ${{ steps.ev.outputs.shouldPush == 'true' }} + with: + subject-name: ${{ steps.ev.outputs.attestImageNames }} + subject-digest: ${{ steps.push.outputs.digest }} + push-to-registry: true ci-website-mark: if: always() needs: - lint - test - - build + - build-container runs-on: ubuntu-latest steps: - uses: re-actors/alls-green@release/v1 diff --git a/.github/workflows/release-publish.yml b/.github/workflows/release-publish.yml index 824038774c55..9ac5119eaf16 100644 --- a/.github/workflows/release-publish.yml +++ b/.github/workflows/release-publish.yml @@ -20,6 +20,49 @@ jobs: release: true registry_dockerhub: true registry_ghcr: true + build-docs: + runs-on: ubuntu-latest + permissions: + # Needed to upload container images to ghcr.io + packages: write + # Needed for attestation + id-token: write + attestations: write + steps: + - uses: actions/checkout@v4 + - name: Set up QEMU + uses: docker/setup-qemu-action@v3.6.0 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: prepare variables + uses: ./.github/actions/docker-push-variables + id: ev + env: + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + with: + image-name: ghcr.io/goauthentik/docs + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Build Docker Image + id: push + uses: docker/build-push-action@v6 + with: + tags: ${{ steps.ev.outputs.imageTags }} + file: website/Dockerfile + push: true + platforms: linux/amd64,linux/arm64 + context: . + - uses: actions/attest-build-provenance@v2 + id: attest + if: true + with: + subject-name: ${{ steps.ev.outputs.attestImageNames }} + subject-digest: ${{ steps.push.outputs.digest }} + push-to-registry: true build-outpost: runs-on: ubuntu-latest permissions: @@ -193,6 +236,6 @@ jobs: SENTRY_ORG: authentik-security-inc SENTRY_PROJECT: authentik with: - version: authentik@${{ steps.ev.outputs.version }} + release: authentik@${{ steps.ev.outputs.version }} sourcemaps: "./web/dist" url_prefix: "~/static/dist" diff --git a/Dockerfile b/Dockerfile index 0b2af3154520..63e07d34c19f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,26 +1,7 @@ # syntax=docker/dockerfile:1 -# Stage 1: Build website -FROM --platform=${BUILDPLATFORM} docker.io/library/node:24 AS website-builder - -ENV NODE_ENV=production - -WORKDIR /work/website - -RUN --mount=type=bind,target=/work/website/package.json,src=./website/package.json \ - --mount=type=bind,target=/work/website/package-lock.json,src=./website/package-lock.json \ - --mount=type=cache,id=npm-website,sharing=shared,target=/root/.npm \ - npm ci --include=dev - -COPY ./website /work/website/ -COPY ./blueprints /work/blueprints/ -COPY ./schema.yml /work/ -COPY ./SECURITY.md /work/ - -RUN npm run build-bundled - -# Stage 2: Build webui -FROM --platform=${BUILDPLATFORM} docker.io/library/node:24 AS web-builder +# Stage 1: Build webui +FROM --platform=${BUILDPLATFORM} docker.io/library/node:24-slim AS node-builder ARG GIT_BUILD_HASH ENV GIT_BUILD_HASH=$GIT_BUILD_HASH @@ -32,7 +13,7 @@ RUN --mount=type=bind,target=/work/web/package.json,src=./web/package.json \ --mount=type=bind,target=/work/web/package-lock.json,src=./web/package-lock.json \ --mount=type=bind,target=/work/web/packages/sfe/package.json,src=./web/packages/sfe/package.json \ --mount=type=bind,target=/work/web/scripts,src=./web/scripts \ - --mount=type=cache,id=npm-web,sharing=shared,target=/root/.npm \ + --mount=type=cache,id=npm-ak,sharing=shared,target=/root/.npm \ npm ci --include=dev COPY ./package.json /work @@ -43,7 +24,7 @@ COPY ./gen-ts-api /work/web/node_modules/@goauthentik/api RUN npm run build && \ npm run build:sfe -# Stage 3: Build go proxy +# Stage 2: Build go proxy FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.24-bookworm AS go-builder ARG TARGETOS @@ -68,8 +49,8 @@ RUN --mount=type=bind,target=/go/src/goauthentik.io/go.mod,src=./go.mod \ COPY ./cmd /go/src/goauthentik.io/cmd COPY ./authentik/lib /go/src/goauthentik.io/authentik/lib COPY ./web/static.go /go/src/goauthentik.io/web/static.go -COPY --from=web-builder /work/web/robots.txt /go/src/goauthentik.io/web/robots.txt -COPY --from=web-builder /work/web/security.txt /go/src/goauthentik.io/web/security.txt +COPY --from=node-builder /work/web/robots.txt /go/src/goauthentik.io/web/robots.txt +COPY --from=node-builder /work/web/security.txt /go/src/goauthentik.io/web/security.txt COPY ./internal /go/src/goauthentik.io/internal COPY ./go.mod /go/src/goauthentik.io/go.mod COPY ./go.sum /go/src/goauthentik.io/go.sum @@ -80,7 +61,7 @@ RUN --mount=type=cache,sharing=locked,target=/go/pkg/mod \ CGO_ENABLED=1 GOFIPS140=latest GOARM="${TARGETVARIANT#v}" \ go build -o /go/authentik ./cmd/server -# Stage 4: MaxMind GeoIP +# Stage 3: MaxMind GeoIP FROM --platform=${BUILDPLATFORM} ghcr.io/maxmind/geoipupdate:v7.1.0 AS geoip ENV GEOIPUPDATE_EDITION_IDS="GeoLite2-City GeoLite2-ASN" @@ -93,9 +74,9 @@ RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \ mkdir -p /usr/share/GeoIP && \ /bin/sh -c "GEOIPUPDATE_LICENSE_KEY_FILE=/run/secrets/GEOIPUPDATE_LICENSE_KEY /usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0" -# Stage 5: Download uv +# Stage 4: Download uv FROM ghcr.io/astral-sh/uv:0.7.11 AS uv -# Stage 6: Base python image +# Stage 5: Base python image FROM ghcr.io/goauthentik/fips-python:3.13.3-slim-bookworm-fips AS python-base ENV VENV_PATH="/ak-root/.venv" \ @@ -109,7 +90,7 @@ WORKDIR /ak-root/ COPY --from=uv /uv /uvx /bin/ -# Stage 7: Python dependencies +# Stage 6: Python dependencies FROM python-base AS python-deps ARG TARGETARCH @@ -144,7 +125,7 @@ RUN --mount=type=bind,target=pyproject.toml,src=pyproject.toml \ --mount=type=cache,target=/root/.cache/uv \ uv sync --frozen --no-install-project --no-dev -# Stage 8: Run +# Stage 7: Run FROM python-base AS final-image ARG VERSION @@ -187,9 +168,8 @@ COPY ./lifecycle/ /lifecycle COPY ./authentik/sources/kerberos/krb5.conf /etc/krb5.conf COPY --from=go-builder /go/authentik /bin/authentik COPY --from=python-deps /ak-root/.venv /ak-root/.venv -COPY --from=web-builder /work/web/dist/ /web/dist/ -COPY --from=web-builder /work/web/authentik/ /web/authentik/ -COPY --from=website-builder /work/website/build/ /website/help/ +COPY --from=node-builder /work/web/dist/ /web/dist/ +COPY --from=node-builder /work/web/authentik/ /web/authentik/ COPY --from=geoip /usr/share/GeoIP /geoip USER 1000 diff --git a/internal/web/static.go b/internal/web/static.go index 62fa775593eb..25cdcaaed52f 100644 --- a/internal/web/static.go +++ b/internal/web/static.go @@ -31,8 +31,6 @@ func (ws *WebServer) configureStatic() { return h } - helpHandler := http.FileServer(http.Dir("./website/help/")) - indexLessRouter.PathPrefix(config.Get().Web.Path).PathPrefix("/static/dist/").Handler(pathStripper( distFs, "static/dist/", @@ -78,13 +76,6 @@ func (ws *WebServer) configureStatic() { )) } - staticRouter.PathPrefix(config.Get().Web.Path).PathPrefix("/if/help/").Handler(pathStripper( - helpHandler, - config.Get().Web.Path, - "/if/help/", - )) - staticRouter.PathPrefix(config.Get().Web.Path).PathPrefix("/help").Handler(http.RedirectHandler(fmt.Sprintf("%sif/help/", config.Get().Web.Path), http.StatusMovedPermanently)) - staticRouter.PathPrefix(config.Get().Web.Path).Path("/robots.txt").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { rw.Header()["Content-Type"] = []string{"text/plain"} rw.WriteHeader(200) diff --git a/website/Dockerfile b/website/Dockerfile new file mode 100644 index 000000000000..849df022f1a2 --- /dev/null +++ b/website/Dockerfile @@ -0,0 +1,21 @@ +FROM --platform=${BUILDPLATFORM} docker.io/library/node:24 AS website-builder + +ENV NODE_ENV=production + +WORKDIR /work/website + +RUN --mount=type=bind,target=/work/website/package.json,src=./website/package.json \ + --mount=type=bind,target=/work/website/package-lock.json,src=./website/package-lock.json \ + --mount=type=cache,id=npm-website,sharing=shared,target=/root/.npm \ + npm ci --include=dev + +COPY ./website /work/website/ +COPY ./blueprints /work/blueprints/ +COPY ./schema.yml /work/ +COPY ./SECURITY.md /work/ + +RUN npm run build-bundled + +FROM docker.io/library/nginx:1.27.5 + +COPY --from=website-builder /work/website/build /usr/share/nginx/html