Skip to content

Reduce incremental Docker build times #27998

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,7 @@ prime/

# Manpage
/man

Dockerfile
.dockerignore
.github/
1 change: 0 additions & 1 deletion .github/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ modifies/migrations:
modifies/internal:
- "Makefile"
- "Dockerfile"
- "Dockerfile.rootless"
- "docker/**"
- "webpack.config.js"
- ".eslintrc.yaml"
Expand Down
1 change: 0 additions & 1 deletion .github/workflows/files-changed.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ jobs:

docker:
- "Dockerfile"
- "Dockerfile.rootless"
- "docker/**"
- "Makefile"

Expand Down
14 changes: 5 additions & 9 deletions .github/workflows/pull-docker-dryrun.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,21 @@ jobs:
files-changed:
uses: ./.github/workflows/files-changed.yml

regular:
docker:
if: needs.files-changed.outputs.docker == 'true' || needs.files-changed.outputs.actions == 'true'
needs: files-changed
runs-on: ubuntu-latest
steps:
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v5
with:
target: gitea
pull: true
push: false
tags: gitea/gitea:linux-amd64

rootless:
if: needs.files-changed.outputs.docker == 'true' || needs.files-changed.outputs.actions == 'true'
needs: files-changed
runs-on: ubuntu-latest
steps:
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v5
with:
target: gitea-rootless
pull: true
push: false
file: Dockerfile.rootless
tags: gitea/gitea:linux-amd64
37 changes: 5 additions & 32 deletions .github/workflows/release-nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ jobs:
- name: upload binaries to s3
run: |
aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress
nightly-docker-rootful:
nightly-docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -91,44 +91,17 @@ jobs:
uses: docker/build-push-action@v5
with:
context: .
target: gitea
platforms: linux/amd64,linux/arm64
pull: true
push: true
tags: gitea/gitea:${{ steps.clean_name.outputs.branch }}
nightly-docker-rootless:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
- run: git fetch --unshallow --quiet --tags --force
- uses: actions/setup-go@v4
with:
go-version-file: go.mod
check-latest: true
- uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3
- name: Get cleaned branch name
id: clean_name
run: |
# if main then say nightly otherwise cleanup name
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
echo "branch=nightly" >> "$GITHUB_OUTPUT"
exit 0
fi
REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//')
echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT"
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: fetch go modules
run: make vendor
- name: build rootless docker image
uses: docker/build-push-action@v5
with:
context: .
target: gitea-rootless
platforms: linux/amd64,linux/arm64
pull: true
push: true
file: Dockerfile.rootless
tags: gitea/gitea:${{ steps.clean_name.outputs.branch }}-rootless
5 changes: 4 additions & 1 deletion .github/workflows/release-tag-rc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,9 @@ jobs:
uses: docker/build-push-action@v5
with:
context: .
target: gitea
platforms: linux/amd64,linux/arm64
pull: true
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
Expand Down Expand Up @@ -118,8 +120,9 @@ jobs:
uses: docker/build-push-action@v5
with:
context: .
target: gitea-rootless
platforms: linux/amd64,linux/arm64
pull: true
push: true
file: Dockerfile.rootless
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
5 changes: 4 additions & 1 deletion .github/workflows/release-tag-version.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,9 @@ jobs:
uses: docker/build-push-action@v5
with:
context: .
target: gitea
platforms: linux/amd64,linux/arm64
pull: true
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
Expand Down Expand Up @@ -134,8 +136,9 @@ jobs:
uses: docker/build-push-action@v5
with:
context: .
target: gitea-rootless
platforms: linux/amd64,linux/arm64
pull: true
push: true
file: Dockerfile.rootless
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
160 changes: 127 additions & 33 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,40 @@
# Build stage
FROM docker.io/library/golang:1.21-alpine3.18 AS build-env
FROM docker.io/library/node:20-alpine3.18 AS build-frontend

ARG GITEA_VERSION

# Build deps
RUN apk --no-cache add \
build-base \
git \
&& rm -rf /var/cache/apk/*

# Setup repo
WORKDIR /usr/src/code.gitea.io/gitea

COPY Makefile .

# Download NPM Packages
COPY package.json .
COPY package-lock.json .

RUN make deps-frontend

# Copy source files
COPY ./webpack.config.js .
COPY ./assets ./assets
COPY ./public ./public
COPY ./web_src ./web_src

# Checkout version if set
COPY ./.git ./.git
RUN if [ -n "${GITEA_VERSION}" ]; then git checkout "${GITEA_VERSION}"; fi
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thoughts on removing this line, can see it has been untouched in this file for 5 years so guessing it might be a part of a workflow?

Disadavntage of including these lines is that any time a maintainer commits to git, they will be re-running the COPY ./.git ./.git step. Currently this happend transparently.

To replicate the functionality you can use this feature of docker build

docker build https://github.com/go-gitea/gitea.git#v1.20.5

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is due to the makefile using git calls to determine the version information. This could (and tbh should) be pulled out for the reasons you mentioned that whenever git repo changes it invalidates the cache (even if you are attempting to build the same tag). We do some magic in the makefile to calculate the version, and this logic is used for more than just docker (eg xgo crossarch builds) which means it's kinda load bearing and changes to it need a closer eye when reviewing.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at the failed build, the .git folder isn't present during CI runs - I think this is more to do with the dyrun workflow needing to do a full checkout once it has detected a file has changed?

In the Dockerfile GITEA_VERSION is an ARG, from what I can tell these are only accessible within the Dockerfile, and aren't in the build as environment variables, so should be safe to take out?

I think in the GitHub release workflows, it is using the .git directory to determine version information.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if we were to go down the route of not relying on .git for Docker builds, we may want to go down the route of something like....

ARG GITEA_VERSION
ENV GITEA_VERSION=${GITEA_VERSION:-develop}
RUN echo "${GITEA_VERSION}" > VERSION

This way, when developing you will get a nominal "develop" value for your Gitea version, but when compiling for release, it can be specified as a build argument?


# Build frontend
RUN make clean-all frontend

# Build stage
FROM docker.io/library/golang:1.21-alpine3.18 AS build-backend

ARG GOPROXY
ENV GOPROXY ${GOPROXY:-direct}
Expand All @@ -13,64 +48,126 @@ ARG CGO_EXTRA_CFLAGS
RUN apk --no-cache add \
build-base \
git \
nodejs \
npm \
&& rm -rf /var/cache/apk/*

# Setup repo
COPY . ${GOPATH}/src/code.gitea.io/gitea
WORKDIR ${GOPATH}/src/code.gitea.io/gitea

COPY Makefile .

# Download Golang Modules
COPY go.mod .
COPY go.sum .

RUN make deps-backend

# Copy source files
COPY ./build ./build
COPY ./cmd ./cmd
COPY ./models ./models
COPY ./modules ./modules
COPY ./options ./options
COPY ./routers ./routers
COPY ./services ./services
COPY ./templates ./templates
COPY ./build.go .
COPY ./main.go .

# Checkout version if set
RUN if [ -n "${GITEA_VERSION}" ]; then git checkout "${GITEA_VERSION}"; fi \
&& make clean-all build
COPY ./.git ./.git
RUN if [ -n "${GITEA_VERSION}" ]; then git checkout "${GITEA_VERSION}"; fi

# Clean directory
RUN make clean-all

# Copy frontend build artifacts
COPY --from=build-frontend /usr/src/code.gitea.io/gitea/public ./public

# Build backend
RUN make backend

# Begin env-to-ini build
COPY contrib/environment-to-ini/environment-to-ini.go contrib/environment-to-ini/environment-to-ini.go
COPY ./custom ./custom

RUN go build contrib/environment-to-ini/environment-to-ini.go

# Copy local files
COPY docker/root /tmp/local

# Set permissions
RUN chmod 755 /tmp/local/usr/bin/entrypoint \
/tmp/local/usr/local/bin/gitea \
/tmp/local/etc/s6/gitea/* \
/tmp/local/etc/s6/openssh/* \
/tmp/local/etc/s6/.s6-svscan/* \
/go/src/code.gitea.io/gitea/gitea \
/go/src/code.gitea.io/gitea/environment-to-ini
RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete

FROM docker.io/library/alpine:3.18
FROM docker.io/library/alpine:3.18 AS gitea-base
LABEL maintainer="[email protected]"

EXPOSE 22 3000

RUN apk --no-cache add \
bash \
ca-certificates \
curl \
gettext \
git \
curl \
gnupg \
&& rm -rf /var/cache/apk/*

RUN addgroup -S -g 1000 git

COPY --chmod=644 ./contrib/autocompletion/bash_autocomplete /etc/profile.d/gitea_bash_autocomplete.sh
COPY --chmod=755 --from=build-backend /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea
COPY --chmod=755 --from=build-backend /go/src/code.gitea.io/gitea/environment-to-ini /usr/local/bin/environment-to-ini

FROM gitea-base AS gitea-rootless
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this build work with the two tags in the same file? Would target: gitea in docker/build-push-action skip over these steps for gitea-rootless? I assume not, right?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can see in the docker-dryrun workflow, that the first build-push-action build gitea-base and gitea. Then in the next build-push-action you can see the gitea-base layers have all been cached and only the gitea-rootless layers are run.

If not specifying the target when building, docker build will export the final image "as the one to run".


EXPOSE 2222 3000

RUN apk --no-cache add \
dumb-init \
&& rm -rf /var/cache/apk/*

RUN adduser \
-S -H -D \
-h /var/lib/gitea/git \
-s /bin/bash \
-u 1000 \
-G git \
git

RUN mkdir -p /var/lib/gitea /etc/gitea
RUN chown git:git /var/lib/gitea /etc/gitea

# Copy local files
COPY --chmod=755 docker/rootless /

# git:git
USER 1000:1000
ENV GITEA_WORK_DIR /var/lib/gitea
ENV GITEA_CUSTOM /var/lib/gitea/custom
ENV GITEA_TEMP /tmp/gitea
ENV TMPDIR /tmp/gitea

# TODO add to docs the ability to define the ini to load (useful to test and revert a config)
ENV GITEA_APP_INI /etc/gitea/app.ini
ENV HOME "/var/lib/gitea/git"
VOLUME ["/var/lib/gitea", "/etc/gitea"]
WORKDIR /var/lib/gitea

ENTRYPOINT ["/usr/bin/dumb-init", "--", "/usr/local/bin/docker-entrypoint.sh"]
CMD []

FROM gitea-base AS gitea

EXPOSE 22 3000

RUN apk --no-cache add \
linux-pam \
openssh \
s6 \
sqlite \
su-exec \
gnupg \
&& rm -rf /var/cache/apk/*

RUN addgroup \
-S -g 1000 \
git && \
adduser \
RUN adduser \
-S -H -D \
-h /data/git \
-s /bin/bash \
-u 1000 \
-G git \
git && \
echo "git:*" | chpasswd -e
echo "git:*" | chpasswd -e

ENV USER git
ENV GITEA_CUSTOM /data/gitea
Expand All @@ -80,7 +177,4 @@ VOLUME ["/data"]
ENTRYPOINT ["/usr/bin/entrypoint"]
CMD ["/bin/s6-svscan", "/etc/s6"]

COPY --from=build-env /tmp/local /
COPY --from=build-env /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea
COPY --from=build-env /go/src/code.gitea.io/gitea/environment-to-ini /usr/local/bin/environment-to-ini
COPY --from=build-env /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete /etc/profile.d/gitea_bash_autocomplete.sh
COPY --chmod=755 docker/root /
Loading