From 33d26804a0c45783986345b989baeea8d42006d9 Mon Sep 17 00:00:00 2001 From: Daniel Legt Date: Sat, 30 May 2026 14:36:02 +0300 Subject: [PATCH] feat: add Docker support and Gitea publish workflow - Add a multi-stage Dockerfile for building and running the Go backend - Add .dockerignore to optimize the Docker build context - Create a Gitea Actions workflow to build and publish Docker images on tag push - Register a new `/health` endpoint for container healthchecks - Update README.md with Docker and Podman deployment instructions - Ignore local `docker-compose.yml` in .gitignore --- .dockerignore | 23 ++++++++++++++ .gitea/workflows/publish.yml | 46 ++++++++++++++++++++++++++++ .gitignore | 1 + Dockerfile | 40 ++++++++++++++++++++++++ README.md | 19 ++++++++++++ backend/libs/handlers/app.go | 1 + backend/libs/handlers/health_test.go | 28 +++++++++++++++++ docker-compose.example.yml | 24 +++++++++++++++ 8 files changed, 182 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitea/workflows/publish.yml create mode 100644 Dockerfile create mode 100644 backend/libs/handlers/health_test.go create mode 100644 docker-compose.example.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..5a1d380 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,23 @@ +.git +.gitea +.github +.idea +.vscode + +data +backend/warpbox +warpbox + +.env +scripts/env/*.env +!scripts/env/*.env.example + +*.log +*.tmp +*.swp +.DS_Store + +docs/Mock-Up/Mockup Magic/node_modules +docs/Mock-Up/Mockup Magic/.tanstack +docs/Mock-Up/Mockup Magic/dist +docs/Mock-Up/Mockup Magic/.wrangler diff --git a/.gitea/workflows/publish.yml b/.gitea/workflows/publish.yml new file mode 100644 index 0000000..415e8b2 --- /dev/null +++ b/.gitea/workflows/publish.yml @@ -0,0 +1,46 @@ +name: Build and Publish Docker Image +run-name: Publishing ${{ gitea.ref_name }} + +on: + push: + tags: + - "v*" + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version-file: backend/go.mod + cache: false + + - name: Run Tests + run: cd backend && go test ./... + + - name: Install Docker + run: curl -fsSL https://get.docker.com | sh + + - name: Build Docker Image + run: | + docker build \ + --build-arg APP_VERSION=${{ gitea.ref_name }} \ + -t tea.chunkbyte.com/kato/warpbox-dev:${{ gitea.ref_name }} \ + -t tea.chunkbyte.com/kato/warpbox-dev:latest \ + . + + - name: Login to Gitea Container Registry + uses: docker/login-action@v3 + with: + registry: tea.chunkbyte.com + username: ${{ secrets.REGISTRY_USERNAME }} + password: ${{ secrets.REGISTRY_PASSWORD }} + + - name: Push Docker Image + run: | + docker push tea.chunkbyte.com/kato/warpbox-dev:${{ gitea.ref_name }} + docker push tea.chunkbyte.com/kato/warpbox-dev:latest diff --git a/.gitignore b/.gitignore index 3499abf..971188b 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ backend/static/uploads/* .env.* !.env.example scripts/env/dev.env +docker-compose.yml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..78bab71 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,40 @@ +FROM golang:1.26-alpine AS builder + +WORKDIR /src/backend + +COPY backend/go.mod backend/go.sum ./ +RUN go mod download + +COPY backend/ ./ + +ARG APP_VERSION=dev +RUN CGO_ENABLED=0 GOOS=linux go build \ + -trimpath \ + -ldflags="-s -w -X main.version=${APP_VERSION}" \ + -o /out/warpbox \ + ./cmd/warpbox + +FROM alpine:3.22 + +RUN apk add --no-cache ca-certificates ffmpeg wget + +ENV WARPBOX_ADDR=:8080 \ + WARPBOX_DATA_DIR=/data \ + WARPBOX_STATIC_DIR=/app/static \ + WARPBOX_TEMPLATE_DIR=/app/templates + +WORKDIR /app + +COPY --from=builder /out/warpbox /usr/local/bin/warpbox +COPY backend/static /app/static +COPY backend/templates /app/templates + +RUN mkdir -p /data + +EXPOSE 8080 +VOLUME ["/data"] + +HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \ + CMD wget -qO- http://127.0.0.1:8080/health >/dev/null || exit 1 + +ENTRYPOINT ["warpbox"] diff --git a/README.md b/README.md index c46bc3d..15e9a7c 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,25 @@ cd backend go run ./cmd/warpbox ``` +## Docker / Podman + +Copy the example environment file and adjust values such as `WARPBOX_BASE_URL` and +`WARPBOX_ADMIN_TOKEN` before running the container: + +Copy the example [docker-compose.example.yml](./docker-compose.example.yml) to [docker-compose.yml](./docker-compose.yml), modify as need-be + +```bash +cp .env.example .env +docker compose -f docker-compose.yml up --build +``` + +The compose example also works with Podman compatible compose tools. Its data volume uses +`./data:/data:Z` for SELinux relabeling, and the container overrides runtime paths to use +`/data`, `/app/static`, and `/app/templates`. + +The image exposes `/health`, `/healthz`, and `/api/v1/health`. Docker and compose healthchecks +use `/health`. + ## Layout - `backend/cmd/warpbox` - main application entry point. diff --git a/backend/libs/handlers/app.go b/backend/libs/handlers/app.go index a2a375b..40c9d22 100644 --- a/backend/libs/handlers/app.go +++ b/backend/libs/handlers/app.go @@ -44,6 +44,7 @@ func (a *App) RegisterRoutes(mux *http.ServeMux) { mux.HandleFunc("GET /d/{boxID}/f/{fileID}", a.DownloadFile) mux.HandleFunc("GET /d/{boxID}/f/{fileID}/download", a.DownloadFileContent) mux.HandleFunc("GET /d/{boxID}/thumb/{fileID}", a.Thumbnail) + mux.HandleFunc("GET /health", a.Health) mux.HandleFunc("GET /healthz", a.Health) mux.HandleFunc("GET /api/v1/health", a.Health) mux.HandleFunc("GET /api/v1/sharex/warpbox-anonymous.sxcu", a.ShareXAnonymousConfig) diff --git a/backend/libs/handlers/health_test.go b/backend/libs/handlers/health_test.go new file mode 100644 index 0000000..14d42c5 --- /dev/null +++ b/backend/libs/handlers/health_test.go @@ -0,0 +1,28 @@ +package handlers + +import ( + "net/http" + "net/http/httptest" + "testing" +) + +func TestHealthRoutes(t *testing.T) { + app, cleanup := newTestApp(t) + defer cleanup() + + mux := http.NewServeMux() + app.RegisterRoutes(mux) + + for _, path := range []string{"/health", "/healthz", "/api/v1/health"} { + t.Run(path, func(t *testing.T) { + request := httptest.NewRequest(http.MethodGet, path, nil) + response := httptest.NewRecorder() + + mux.ServeHTTP(response, request) + + if response.Code != http.StatusOK { + t.Fatalf("status = %d, body = %s", response.Code, response.Body.String()) + } + }) + } +} diff --git a/docker-compose.example.yml b/docker-compose.example.yml new file mode 100644 index 0000000..e0a0e3f --- /dev/null +++ b/docker-compose.example.yml @@ -0,0 +1,24 @@ +services: + warpbox-dev: + build: + context: . + dockerfile: Dockerfile + image: warpbox-dev:local + env_file: + - .env + environment: + WARPBOX_ADDR: ":8080" + WARPBOX_DATA_DIR: /data + WARPBOX_STATIC_DIR: /app/static + WARPBOX_TEMPLATE_DIR: /app/templates + ports: + - "8080:8080" + volumes: + - ./data:/data:Z + restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8080/health >/dev/null || exit 1"] + interval: 30s + timeout: 3s + start_period: 10s + retries: 3