From 524123e871523fe791e84cf495e402fe76b7f377 Mon Sep 17 00:00:00 2001 From: Manas Maiti Date: Fri, 20 Mar 2026 14:07:15 +0100 Subject: [PATCH 1/2] chore(container): adding container support --- .dockerignore | 10 ++++++++ .github/workflows/ci.yml | 34 +++++++++++++++++++++++++++- Containerfile | 27 ++++++++++++++++++++++ docs/getting-started/index.md | 2 +- docs/getting-started/installation.md | 25 ++++++++++++++++++++ docs/getting-started/production.md | 9 ++++++++ 6 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 .dockerignore create mode 100644 Containerfile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..317f5d4 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +.git +.github +docs +node_modules +frontend/dist +backend/filetree +backend/filetree.exe +*.md +.env +.env.* diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3de3f8e..792edef 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -103,6 +103,38 @@ jobs: - name: Build run: cd frontend && npm run build + docker: + name: Docker + runs-on: ubuntu-latest + needs: [commitlint, backend, gosec, frontend] + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@v6 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GHCR + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push image + uses: docker/build-push-action@v6 + with: + context: . + file: ./Containerfile + push: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} + load: ${{ github.event_name != 'push' || github.ref != 'refs/heads/main' }} + tags: | + ghcr.io/${{ github.repository }}:latest + ghcr.io/${{ github.repository }}:sha-${{ github.sha }} + docs: name: Docs runs-on: ubuntu-latest @@ -125,7 +157,7 @@ jobs: ci-success: name: CI success runs-on: ubuntu-latest - needs: [commitlint, backend, gosec, frontend, docs] + needs: [commitlint, backend, gosec, frontend, docker, docs] steps: - name: All checks passed run: echo "All checks passed" diff --git a/Containerfile b/Containerfile new file mode 100644 index 0000000..3f8b883 --- /dev/null +++ b/Containerfile @@ -0,0 +1,27 @@ +FROM node:20-alpine AS frontend +WORKDIR /app/frontend +COPY frontend/package*.json ./ +RUN npm ci +COPY frontend/ ./ +RUN npm run build + +FROM golang:1.25-alpine AS backend +WORKDIR /app +COPY backend/go.mod backend/go.sum ./ +RUN go mod download +COPY backend/ ./ +RUN CGO_ENABLED=0 go build -o filetree . + +FROM alpine:3.20 +RUN apk add --no-cache ca-certificates +WORKDIR /app + +COPY --from=backend /app/filetree . +COPY --from=frontend /app/frontend/dist ./frontend/dist + +EXPOSE 8080 + +ENV ROOT_PATH=/data +VOLUME /data + +ENTRYPOINT ["./filetree"] diff --git a/docs/getting-started/index.md b/docs/getting-started/index.md index 5b64770..e67e6b1 100644 --- a/docs/getting-started/index.md +++ b/docs/getting-started/index.md @@ -34,7 +34,7 @@ Open **http://localhost:8080**. You'll see the file manager — browse, upload, | Step | What to do | |------|------------| -| [Installation](installation.md) | Choose your path: make, from source, or development mode with hot reload | +| [Installation](installation.md) | Choose your path: make, Docker, from source, or development mode with hot reload | | [Production](production.md) | Deploy to a server — reverse proxy, HTTPS, and production config | | [Configuration](../configuration/index.md) | Set up your config file and environment variables | | [Authentication](../authentication/index.md) | Enable Google OAuth, GitHub OAuth, or local username/password | diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index 1bd1d58..9dc1c87 100644 --- a/docs/getting-started/installation.md +++ b/docs/getting-started/installation.md @@ -26,6 +26,31 @@ Filetree runs as a single binary that serves both the API and the frontend. Prer ROOT_PATH=/path/to/files CONFIG_FILE=./config.yaml ./backend/filetree ``` +=== "with Docker" + + Build and run with Docker or Podman: + + ```bash + docker build -f Containerfile -t filetree . + docker run -p 8080:8080 -v /path/to/files:/data filetree + ``` + + Or with Podman: + + ```bash + podman build -f Containerfile -t filetree . + podman run -p 8080:8080 -v /path/to/files:/data filetree + ``` + + Then open **http://localhost:8080**. The image uses `ROOT_PATH=/data` by default; mount your files at `/data`. + + For custom config: + + ```bash + docker run -p 8080:8080 -v /path/to/files:/data -v /path/to/config.yaml:/app/config.yaml \ + -e CONFIG_FILE=/app/config.yaml filetree + ``` + === "from source" Build the frontend and backend manually: diff --git a/docs/getting-started/production.md b/docs/getting-started/production.md index e6619fd..101994b 100644 --- a/docs/getting-started/production.md +++ b/docs/getting-started/production.md @@ -15,12 +15,21 @@ Then open **http://localhost:8080**. ## Deployment +### Option A: Binary + 1. **Copy** the `filetree` binary and the `frontend/dist` folder to your server 2. **Set** `ROOT_PATH` to the directory where files should be stored 3. **Set** `CONFIG_FILE` to your config file (e.g. `./config.yaml`) 4. **Use** a reverse proxy (nginx, Caddy) for the production URL if needed 5. **Set** `JWT_SECRET` and `oauth_redirect_url` for production (e.g. `https://your-domain.com/api/auth/google/callback`) +### Option B: Docker + +1. **Build** the image: `docker build -f Containerfile -t filetree .` +2. **Run** with a volume for files: `docker run -d -p 8080:8080 -v /path/to/files:/data filetree` +3. **Mount** your config file and set `CONFIG_FILE` if needed +4. **Use** a reverse proxy in front for HTTPS and custom domains + ## Single binary When `frontend/dist` exists next to the binary, the backend serves it directly. No separate web server is required for the frontend. For HTTPS and custom domains, put a reverse proxy in front. From 5c87648f13a35564c1fce0f4d15027506b6eecff Mon Sep 17 00:00:00 2001 From: Manas Maiti Date: Fri, 20 Mar 2026 21:07:24 +0100 Subject: [PATCH 2/2] chore(container): restructure ci --- .github/workflows/ci.yml | 82 +++++++++++++++++++++++++------------- .github/workflows/docs.yml | 63 +++++++++++++++++++++++++---- 2 files changed, 109 insertions(+), 36 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 792edef..8a7d916 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,6 +3,8 @@ name: CI on: push: pull_request: + release: + types: [published] concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -103,61 +105,85 @@ jobs: - name: Build run: cd frontend && npm run build - docker: - name: Docker + docker-build: + name: Build runs-on: ubuntu-latest needs: [commitlint, backend, gosec, frontend] + steps: + - uses: actions/checkout@v6 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build image + uses: docker/build-push-action@v6 + with: + context: . + file: ./Containerfile + push: false + load: true + tags: filetree:test + cache-from: type=gha + cache-to: type=gha,mode=max + + docker-push: + name: Push + runs-on: ubuntu-latest + needs: [docker-build] + if: (github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'release' permissions: contents: read packages: write steps: - uses: actions/checkout@v6 + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Log in to GHCR - if: github.event_name == 'push' && github.ref == 'refs/heads/main' uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Generate Docker tags and labels + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/${{ github.repository }} + tags: | + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + type=ref,event=branch + type=sha,prefix=sha- + flavor: | + latest=${{ github.event_name == 'release' && !github.event.release.prerelease || (github.event_name == 'push' && github.ref == 'refs/heads/main') }} + + - name: Set platforms + if: github.event_name == 'release' + run: echo "PLATFORMS=linux/amd64,linux/arm64,linux/arm/v7" >> $GITHUB_ENV + - name: Build and push image uses: docker/build-push-action@v6 with: context: . file: ./Containerfile - push: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} - load: ${{ github.event_name != 'push' || github.ref != 'refs/heads/main' }} - tags: | - ghcr.io/${{ github.repository }}:latest - ghcr.io/${{ github.repository }}:sha-${{ github.sha }} - - docs: - name: Docs - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.12' - cache: 'pip' - cache-dependency-path: docs/requirements.txt - - - name: Install dependencies - run: pip install -r docs/requirements.txt - - - name: Build - run: mkdocs build --strict + push: true + platforms: ${{ github.event_name == 'release' && env.PLATFORMS || 'linux/amd64' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max ci-success: name: CI success runs-on: ubuntu-latest - needs: [commitlint, backend, gosec, frontend, docker, docs] + needs: [commitlint, backend, gosec, frontend, docker-build] steps: - name: All checks passed run: echo "All checks passed" diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 0af700e..13b78d0 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,4 +1,4 @@ -name: Deploy docs +name: documentation on: push: @@ -6,8 +6,15 @@ on: paths: - 'docs/**' - 'mkdocs.yml' + pull_request: + paths: + - 'docs/**' + - 'mkdocs.yml' workflow_dispatch: +env: + PYTHON_VERSION: '3.12' + permissions: contents: read pages: write @@ -18,29 +25,42 @@ concurrency: cancel-in-progress: false jobs: - deploy: - name: Deploy to GitHub Pages + documentation: + name: Build documentation runs-on: ubuntu-latest environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} + if: github.event_name != 'pull_request' steps: - - uses: actions/checkout@v6 + - name: Checkout repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 + sparse-checkout: | + docs + mkdocs.yml - - name: Set up Python + - name: Set up Python runtime uses: actions/setup-python@v5 with: - python-version: '3.12' - cache: 'pip' + python-version: ${{ env.PYTHON_VERSION }} + cache: pip cache-dependency-path: docs/requirements.txt - name: Install dependencies run: pip install -r docs/requirements.txt - - name: Build + - name: Build documentation run: mkdocs build --strict + - name: Adjust permissions + run: | + chmod -c -R +rX site/ | while read line; do + echo "::warning title=Invalid file permissions automatically fixed::$line" + done + - name: Setup Pages uses: actions/configure-pages@v4 @@ -52,3 +72,30 @@ jobs: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 + + documentation-check: + name: Check documentation + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 + sparse-checkout: | + docs + mkdocs.yml + + - name: Set up Python runtime + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + cache: pip + cache-dependency-path: docs/requirements.txt + + - name: Install dependencies + run: pip install -r docs/requirements.txt + + - name: Build documentation + run: mkdocs build --strict