feat: add Docker support and Gitea publish workflow
All checks were successful
Build and Publish Docker Image / deploy (push) Successful in 1m38s

- 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
This commit is contained in:
2026-05-30 14:36:02 +03:00
parent 3471e2b0cf
commit 33d26804a0
8 changed files with 182 additions and 0 deletions

23
.dockerignore Normal file
View File

@@ -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

View File

@@ -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

1
.gitignore vendored
View File

@@ -13,3 +13,4 @@ backend/static/uploads/*
.env.* .env.*
!.env.example !.env.example
scripts/env/dev.env scripts/env/dev.env
docker-compose.yml

40
Dockerfile Normal file
View File

@@ -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"]

View File

@@ -29,6 +29,25 @@ cd backend
go run ./cmd/warpbox 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 ## Layout
- `backend/cmd/warpbox` - main application entry point. - `backend/cmd/warpbox` - main application entry point.

View File

@@ -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}", a.DownloadFile)
mux.HandleFunc("GET /d/{boxID}/f/{fileID}/download", a.DownloadFileContent) mux.HandleFunc("GET /d/{boxID}/f/{fileID}/download", a.DownloadFileContent)
mux.HandleFunc("GET /d/{boxID}/thumb/{fileID}", a.Thumbnail) mux.HandleFunc("GET /d/{boxID}/thumb/{fileID}", a.Thumbnail)
mux.HandleFunc("GET /health", a.Health)
mux.HandleFunc("GET /healthz", a.Health) mux.HandleFunc("GET /healthz", a.Health)
mux.HandleFunc("GET /api/v1/health", a.Health) mux.HandleFunc("GET /api/v1/health", a.Health)
mux.HandleFunc("GET /api/v1/sharex/warpbox-anonymous.sxcu", a.ShareXAnonymousConfig) mux.HandleFunc("GET /api/v1/sharex/warpbox-anonymous.sxcu", a.ShareXAnonymousConfig)

View File

@@ -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())
}
})
}
}

View File

@@ -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