mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-02-04 13:21:45 +00:00
feat: distroless container additional variant + healthcheck command (#716)
This commit is contained in:
committed by
GitHub
parent
81315790a8
commit
1a41b05f60
16
.github/workflows/build-next.yml
vendored
16
.github/workflows/build-next.yml
vendored
@@ -73,10 +73,24 @@ jobs:
|
|||||||
push: true
|
push: true
|
||||||
tags: ${{ env.DOCKER_IMAGE_NAME }}:next
|
tags: ${{ env.DOCKER_IMAGE_NAME }}:next
|
||||||
file: Dockerfile-prebuilt
|
file: Dockerfile-prebuilt
|
||||||
|
- name: Build and push container image (distroless)
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
id: container-build-push-distroless
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
push: true
|
||||||
|
tags: ${{ env.DOCKER_IMAGE_NAME }}:next-distroless
|
||||||
|
file: Dockerfile-distroless
|
||||||
- name: Container image attestation
|
- name: Container image attestation
|
||||||
uses: actions/attest-build-provenance@v2
|
uses: actions/attest-build-provenance@v2
|
||||||
with:
|
with:
|
||||||
subject-name: "${{ env.DOCKER_IMAGE_NAME }}"
|
subject-name: "${{ env.DOCKER_IMAGE_NAME }}"
|
||||||
subject-digest: ${{ steps.build-push-image.outputs.digest }}
|
subject-digest: ${{ steps.build-push-image.outputs.digest }}
|
||||||
push-to-registry: true
|
push-to-registry: true
|
||||||
|
- name: Container image attestation (distroless)
|
||||||
|
uses: actions/attest-build-provenance@v2
|
||||||
|
with:
|
||||||
|
subject-name: "${{ env.DOCKER_IMAGE_NAME }}"
|
||||||
|
subject-digest: ${{ steps.container-build-push-distroless.outputs.digest }}
|
||||||
|
push-to-registry: true
|
||||||
|
|||||||
34
.github/workflows/release.yml
vendored
34
.github/workflows/release.yml
vendored
@@ -29,14 +29,12 @@ jobs:
|
|||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Set DOCKER_IMAGE_NAME
|
- name: Set DOCKER_IMAGE_NAME
|
||||||
run: |
|
run: |
|
||||||
# Lowercase REPO_OWNER which is required for containers
|
# Lowercase REPO_OWNER which is required for containers
|
||||||
REPO_OWNER=${{ github.repository_owner }}
|
REPO_OWNER=${{ github.repository_owner }}
|
||||||
DOCKER_IMAGE_NAME="ghcr.io/${REPO_OWNER,,}/pocket-id"
|
DOCKER_IMAGE_NAME="ghcr.io/${REPO_OWNER,,}/pocket-id"
|
||||||
echo "DOCKER_IMAGE_NAME=${DOCKER_IMAGE_NAME}" >>${GITHUB_ENV}
|
echo "DOCKER_IMAGE_NAME=${DOCKER_IMAGE_NAME}" >>${GITHUB_ENV}
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
@@ -53,17 +51,24 @@ jobs:
|
|||||||
type=semver,pattern={{version}},prefix=v
|
type=semver,pattern={{version}},prefix=v
|
||||||
type=semver,pattern={{major}}.{{minor}},prefix=v
|
type=semver,pattern={{major}}.{{minor}},prefix=v
|
||||||
type=semver,pattern={{major}},prefix=v
|
type=semver,pattern={{major}},prefix=v
|
||||||
|
- name: Docker metadata (distroless)
|
||||||
|
id: meta-distroless
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: |
|
||||||
|
${{ env.DOCKER_IMAGE_NAME }}
|
||||||
|
tags: |
|
||||||
|
type=semver,pattern={{version}}-distroless,prefix=v
|
||||||
|
type=semver,pattern={{major}}.{{minor}}-distroless,prefix=v
|
||||||
|
type=semver,pattern={{major}}-distroless,prefix=v
|
||||||
- name: Install frontend dependencies
|
- name: Install frontend dependencies
|
||||||
working-directory: frontend
|
working-directory: frontend
|
||||||
run: npm ci
|
run: npm ci
|
||||||
- name: Build frontend
|
- name: Build frontend
|
||||||
working-directory: frontend
|
working-directory: frontend
|
||||||
run: npm run build
|
run: npm run build
|
||||||
|
|
||||||
- name: Build binaries
|
- name: Build binaries
|
||||||
run: sh scripts/development/build-binaries.sh
|
run: sh scripts/development/build-binaries.sh
|
||||||
|
|
||||||
- name: Build and push container image
|
- name: Build and push container image
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
id: container-build-push
|
id: container-build-push
|
||||||
@@ -74,19 +79,32 @@ jobs:
|
|||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
file: Dockerfile-prebuilt
|
file: Dockerfile-prebuilt
|
||||||
|
- name: Build and push container image (distroless)
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
id: container-build-push-distroless
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta-distroless.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta-distroless.outputs.labels }}
|
||||||
|
file: Dockerfile-distroless
|
||||||
- name: Binary attestation
|
- name: Binary attestation
|
||||||
uses: actions/attest-build-provenance@v2
|
uses: actions/attest-build-provenance@v2
|
||||||
with:
|
with:
|
||||||
subject-path: "backend/.bin/pocket-id-**"
|
subject-path: "backend/.bin/pocket-id-**"
|
||||||
|
|
||||||
- name: Container image attestation
|
- name: Container image attestation
|
||||||
uses: actions/attest-build-provenance@v2
|
uses: actions/attest-build-provenance@v2
|
||||||
with:
|
with:
|
||||||
subject-name: "${{ env.DOCKER_IMAGE_NAME }}"
|
subject-name: "${{ env.DOCKER_IMAGE_NAME }}"
|
||||||
subject-digest: ${{ steps.container-build-push.outputs.digest }}
|
subject-digest: ${{ steps.container-build-push.outputs.digest }}
|
||||||
push-to-registry: true
|
push-to-registry: true
|
||||||
|
- name: Container image attestation (distroless)
|
||||||
|
uses: actions/attest-build-provenance@v2
|
||||||
|
with:
|
||||||
|
subject-name: "${{ env.DOCKER_IMAGE_NAME }}"
|
||||||
|
subject-digest: ${{ steps.container-build-push-distroless.outputs.digest }}
|
||||||
|
push-to-registry: true
|
||||||
- name: Upload binaries to release
|
- name: Upload binaries to release
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
@@ -48,5 +48,7 @@ RUN chmod +x /app/pocket-id && \
|
|||||||
EXPOSE 1411
|
EXPOSE 1411
|
||||||
ENV APP_ENV=production
|
ENV APP_ENV=production
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=90s --timeout=5s --start-period=10s --retries=3 CMD [ "/app/pocket-id", "healthcheck" ]
|
||||||
|
|
||||||
ENTRYPOINT ["sh", "/app/docker/entrypoint.sh"]
|
ENTRYPOINT ["sh", "/app/docker/entrypoint.sh"]
|
||||||
CMD ["/app/pocket-id"]
|
CMD ["/app/pocket-id"]
|
||||||
|
|||||||
18
Dockerfile-distroless
Normal file
18
Dockerfile-distroless
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# This Dockerfile embeds a pre-built binary for the given Linux architecture
|
||||||
|
# Binaries must be built using "./scripts/development/build-binaries.sh --docker-only"
|
||||||
|
|
||||||
|
FROM gcr.io/distroless/static-debian12:nonroot
|
||||||
|
|
||||||
|
# TARGETARCH can be "amd64" or "arm64"
|
||||||
|
ARG TARGETARCH
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY ./backend/.bin/pocket-id-linux-${TARGETARCH} /app/pocket-id
|
||||||
|
|
||||||
|
EXPOSE 1411
|
||||||
|
ENV APP_ENV=production
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=90s --timeout=5s --start-period=10s --retries=3 CMD [ "/app/pocket-id", "healthcheck" ]
|
||||||
|
|
||||||
|
CMD ["/app/pocket-id"]
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
# This Dockerfile embeds a pre-built binary for the given Linux architecture
|
# This Dockerfile embeds a pre-built binary for the given Linux architecture
|
||||||
# Binaries must be built using ""./scripts/development/build-binaries.sh --docker-only"
|
# Binaries must be built using "./scripts/development/build-binaries.sh --docker-only"
|
||||||
|
|
||||||
FROM alpine
|
FROM alpine
|
||||||
|
|
||||||
@@ -16,5 +16,7 @@ COPY ./scripts/docker /app/docker
|
|||||||
EXPOSE 1411
|
EXPOSE 1411
|
||||||
ENV APP_ENV=production
|
ENV APP_ENV=production
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=90s --timeout=5s --start-period=10s --retries=3 CMD [ "/app/pocket-id", "healthcheck" ]
|
||||||
|
|
||||||
ENTRYPOINT ["/app/docker/entrypoint.sh"]
|
ENTRYPOINT ["/app/docker/entrypoint.sh"]
|
||||||
CMD ["/app/pocket-id"]
|
CMD ["/app/pocket-id"]
|
||||||
|
|||||||
@@ -11,13 +11,9 @@ import (
|
|||||||
"github.com/pocket-id/pocket-id/backend/internal/common"
|
"github.com/pocket-id/pocket-id/backend/internal/common"
|
||||||
"github.com/pocket-id/pocket-id/backend/internal/job"
|
"github.com/pocket-id/pocket-id/backend/internal/job"
|
||||||
"github.com/pocket-id/pocket-id/backend/internal/utils"
|
"github.com/pocket-id/pocket-id/backend/internal/utils"
|
||||||
"github.com/pocket-id/pocket-id/backend/internal/utils/signals"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Bootstrap() error {
|
func Bootstrap(ctx context.Context) error {
|
||||||
// Get a context that is canceled when the application is stopping
|
|
||||||
ctx := signals.SignalContext(context.Background())
|
|
||||||
|
|
||||||
initApplicationImages()
|
initApplicationImages()
|
||||||
|
|
||||||
// Initialize the tracer and metrics exporter
|
// Initialize the tracer and metrics exporter
|
||||||
|
|||||||
83
backend/internal/cmds/healthcheck.go
Normal file
83
backend/internal/cmds/healthcheck.go
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
package cmds
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
type healthcheckFlags struct {
|
||||||
|
Endpoint string
|
||||||
|
Verbose bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var flags healthcheckFlags
|
||||||
|
|
||||||
|
healthcheckCmd := &cobra.Command{
|
||||||
|
Use: "healthcheck",
|
||||||
|
Short: "Performs a healthcheck of a running Pocket ID instance",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(cmd.Context(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
url := flags.Endpoint + "/healthz"
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||||
|
if err != nil {
|
||||||
|
slog.ErrorContext(ctx,
|
||||||
|
"Failed to create request object",
|
||||||
|
"error", err,
|
||||||
|
"url", url,
|
||||||
|
"ms", time.Since(start).Milliseconds(),
|
||||||
|
)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
slog.ErrorContext(ctx,
|
||||||
|
"Failed to perform request",
|
||||||
|
"error", err,
|
||||||
|
"url", url,
|
||||||
|
"ms", time.Since(start).Milliseconds(),
|
||||||
|
)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
if res.StatusCode < 200 || res.StatusCode >= 300 {
|
||||||
|
if err != nil {
|
||||||
|
slog.ErrorContext(ctx,
|
||||||
|
"Healthcheck failed",
|
||||||
|
"status", res.StatusCode,
|
||||||
|
"url", url,
|
||||||
|
"ms", time.Since(start).Milliseconds(),
|
||||||
|
)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if flags.Verbose {
|
||||||
|
slog.InfoContext(ctx,
|
||||||
|
"Healthcheck succeeded",
|
||||||
|
"status", res.StatusCode,
|
||||||
|
"url", url,
|
||||||
|
"ms", time.Since(start).Milliseconds(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
healthcheckCmd.Flags().StringVarP(&flags.Endpoint, "endpoint", "e", "http://localhost:"+common.EnvConfig.Port, "Endpoint for Pocket ID")
|
||||||
|
healthcheckCmd.Flags().BoolVarP(&flags.Verbose, "verbose", "v", false, "Enable verbose mode")
|
||||||
|
|
||||||
|
rootCmd.AddCommand(healthcheckCmd)
|
||||||
|
}
|
||||||
@@ -1,12 +1,14 @@
|
|||||||
package cmds
|
package cmds
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/pocket-id/pocket-id/backend/internal/bootstrap"
|
"github.com/pocket-id/pocket-id/backend/internal/bootstrap"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/utils/signals"
|
||||||
)
|
)
|
||||||
|
|
||||||
var rootCmd = &cobra.Command{
|
var rootCmd = &cobra.Command{
|
||||||
@@ -15,7 +17,7 @@ var rootCmd = &cobra.Command{
|
|||||||
Long: "By default, this command starts the pocket-id server.",
|
Long: "By default, this command starts the pocket-id server.",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
// Start the server
|
// Start the server
|
||||||
err := bootstrap.Bootstrap()
|
err := bootstrap.Bootstrap(cmd.Context())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to run pocket-id", "error", err)
|
slog.Error("Failed to run pocket-id", "error", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -24,7 +26,10 @@ var rootCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Execute() {
|
func Execute() {
|
||||||
err := rootCmd.Execute()
|
// Get a context that is canceled when the application is stopping
|
||||||
|
ctx := signals.SignalContext(context.Background())
|
||||||
|
|
||||||
|
err := rootCmd.ExecuteContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ services:
|
|||||||
- "./data:/app/data"
|
- "./data:/app/data"
|
||||||
# Optional healthcheck
|
# Optional healthcheck
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: "curl -f http://localhost:1411/healthz"
|
test: [ "CMD", "/app/pocket-id", "healthcheck" ]
|
||||||
interval: 1m30s
|
interval: 1m30s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 2
|
retries: 2
|
||||||
start_period: 10s
|
start_period: 10s
|
||||||
|
|||||||
Reference in New Issue
Block a user