[v2,2/2] Deploy docs to docs.libcamera.org
diff mbox series

Message ID 20260511134817.2841886-3-stefan.klug@ideasonboard.com
State Superseded
Headers show
Series
  • Deploy documentation to docs.libcamera.org
Related show

Commit Message

Stefan Klug May 11, 2026, 1:48 p.m. UTC
Add a build step that deploys the docs to a server. This step
only runs on pushes to master and on tag builds. It requires three
external variables:

DEPLOY_SSH_KEY_BASE64: base64 encoded private deploy key
DEPLOY_KNOWN_HOSTS: Known hosts containing the destination
DEPLOY_DESTINAION: user@host:/ for the rsync push

Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>

---

Changes in v2:

- Moved deployment into script
- Improved build rules
- Pipe ssh key into ssh agent instead of creating a file
---
 .gitlab-ci/deploy-libcamera-docs.sh | 23 +++++++++++++++++++++++
 .gitlab-ci/setup-container.sh       |  2 ++
 gitlab-ci.yml                       | 29 ++++++++++++++++++++++++++++-
 3 files changed, 53 insertions(+), 1 deletion(-)
 create mode 100755 .gitlab-ci/deploy-libcamera-docs.sh

Comments

Barnabás Pőcze May 12, 2026, 9:02 a.m. UTC | #1
Hi

2026. 05. 11. 15:48 keltezéssel, Stefan Klug írta:
> Add a build step that deploys the docs to a server. This step
> only runs on pushes to master and on tag builds. It requires three
> external variables:
> 
> DEPLOY_SSH_KEY_BASE64: base64 encoded private deploy key
> DEPLOY_KNOWN_HOSTS: Known hosts containing the destination
> DEPLOY_DESTINAION: user@host:/ for the rsync push
> 
> Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
> 
> ---
> 
> Changes in v2:
> 
> - Moved deployment into script
> - Improved build rules
> - Pipe ssh key into ssh agent instead of creating a file
> ---
>   .gitlab-ci/deploy-libcamera-docs.sh | 23 +++++++++++++++++++++++
>   .gitlab-ci/setup-container.sh       |  2 ++
>   gitlab-ci.yml                       | 29 ++++++++++++++++++++++++++++-
>   3 files changed, 53 insertions(+), 1 deletion(-)
>   create mode 100755 .gitlab-ci/deploy-libcamera-docs.sh
> 
> diff --git a/.gitlab-ci/deploy-libcamera-docs.sh b/.gitlab-ci/deploy-libcamera-docs.sh
> new file mode 100755
> index 000000000000..a6936728c4a9
> --- /dev/null
> +++ b/.gitlab-ci/deploy-libcamera-docs.sh
> @@ -0,0 +1,23 @@
> +#!/bin/bash
> +
> +# SPDX-License-Identifier: GPL-2.0-or-later
> +# SPDX-FileCopyrightText: © 2026 Ideas on Board
> +#
> +# Deploy libcamera docs
> +
> +set -e
> +
> +source "$(dirname "$0")/lib.sh"
> +
> +libcamera_deploy_docs() {
> +	echo "Deploying libcamera docs"
> +
> +	rsync -rlz --delete --chmod=Do=rx,Fo=r \
> +          --exclude .doctrees \
> +          --exclude .buildinfo \
> +          --exclude objects.inv \
> +          docs/ \
> +          "${DEPLOY_DESTINATION}${CI_COMMIT_REF_NAME}"
> +}
> +
> +run libcamera_deploy_docs
> diff --git a/.gitlab-ci/setup-container.sh b/.gitlab-ci/setup-container.sh
> index c7ca6426c3ab..6c4b439b887b 100755
> --- a/.gitlab-ci/setup-container.sh
> +++ b/.gitlab-ci/setup-container.sh
> @@ -132,6 +132,8 @@ case $FDO_DISTRIBUTION_VERSION in
>   		python3-sphinxcontrib.doxylink
>   		texlive-latex-extra
>   	)
> +	# Packages required to deploy the documentation
> +	PKGS_LIBCAMERA_RUNTIME+=( openssh-client rsync )
>   	# Tools requires by the lint jobs.
>   	PKGS_LIBCAMERA_RUNTIME+=( clang-format jq python3-autopep8 reuse shellcheck )
>   	# libclang-rt-dev for the clang ASan runtime.
> diff --git a/gitlab-ci.yml b/gitlab-ci.yml
> index 7c5e59de30a1..cef5a16de20a 100644
> --- a/gitlab-ci.yml
> +++ b/gitlab-ci.yml
> @@ -5,6 +5,7 @@ stages:
>     - build
>     - lint
>     - test
> +  - deploy
>   
>   variables:
>     FDO_UPSTREAM_REPO: 'camera/libcamera'
> @@ -74,7 +75,7 @@ include:
>   .libcamera-ci.debian:13:
>     variables:
>       FDO_DISTRIBUTION_VERSION: 'trixie'
> -    FDO_DISTRIBUTION_TAG: '2026-04-24.0'
> +    FDO_DISTRIBUTION_TAG: '2026-05-06.0'
>   
>   .container-debian:
>     extends:
> @@ -438,3 +439,29 @@ test-lc-compliance:virtual:
>     artifacts:
>       reports:
>         junit: build/lc-compliance-report.xml
> +
> +# ------------------------------------------------------------------------------
> +# Deploy stage - deploy docs if we are building master or a tag
> +# ------------------------------------------------------------------------------
> +
> +deploy-docs:
> +  extends:
> +    - .fdo.distribution-image@debian
> +    - .libcamera-ci.debian:13
> +    - .libcamera-ci.scripts
> +  rules:
> +    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
> +    - if: $CI_COMMIT_TAG =~ /^v([0-9]+\.)*[0-9]+$/
> +  environment: deploy

production ? To me the name "deploy" does not seem too descriptive,
and it also matches the name of the stage, which may be confusing.

> +  stage: deploy
> +  needs:
> +    - job: build-docs
> +  script:
> +    - |
> +      mkdir -p ~/.ssh
> +      chmod 700 ~/.ssh
> +      echo "${DEPLOY_KNOWN_HOSTS}" > ~/.ssh/known_hosts
> +      eval $(ssh-agent -s)
> +      echo "${DEPLOY_SSH_KEY_BASE64}" | base64 -d | ssh-add -

Why are these all in a single element of the `script` list?


> +
> +    - $CI_PROJECT_DIR/.gitlab-ci/deploy-libcamera-docs.sh
Stefan Klug May 12, 2026, 10:39 a.m. UTC | #2
Hi,

Thank you for the review.

Quoting Barnabás Pőcze (2026-05-12 11:02:48)
> Hi
> 
> 2026. 05. 11. 15:48 keltezéssel, Stefan Klug írta:
> > Add a build step that deploys the docs to a server. This step
> > only runs on pushes to master and on tag builds. It requires three
> > external variables:
> > 
> > DEPLOY_SSH_KEY_BASE64: base64 encoded private deploy key
> > DEPLOY_KNOWN_HOSTS: Known hosts containing the destination
> > DEPLOY_DESTINAION: user@host:/ for the rsync push
> > 
> > Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
> > 
> > ---
> > 
> > Changes in v2:
> > 
> > - Moved deployment into script
> > - Improved build rules
> > - Pipe ssh key into ssh agent instead of creating a file
> > ---
> >   .gitlab-ci/deploy-libcamera-docs.sh | 23 +++++++++++++++++++++++
> >   .gitlab-ci/setup-container.sh       |  2 ++
> >   gitlab-ci.yml                       | 29 ++++++++++++++++++++++++++++-
> >   3 files changed, 53 insertions(+), 1 deletion(-)
> >   create mode 100755 .gitlab-ci/deploy-libcamera-docs.sh
> > 
> > diff --git a/.gitlab-ci/deploy-libcamera-docs.sh b/.gitlab-ci/deploy-libcamera-docs.sh
> > new file mode 100755
> > index 000000000000..a6936728c4a9
> > --- /dev/null
> > +++ b/.gitlab-ci/deploy-libcamera-docs.sh
> > @@ -0,0 +1,23 @@
> > +#!/bin/bash
> > +
> > +# SPDX-License-Identifier: GPL-2.0-or-later
> > +# SPDX-FileCopyrightText: © 2026 Ideas on Board
> > +#
> > +# Deploy libcamera docs
> > +
> > +set -e
> > +
> > +source "$(dirname "$0")/lib.sh"
> > +
> > +libcamera_deploy_docs() {
> > +     echo "Deploying libcamera docs"
> > +
> > +     rsync -rlz --delete --chmod=Do=rx,Fo=r \
> > +          --exclude .doctrees \
> > +          --exclude .buildinfo \
> > +          --exclude objects.inv \
> > +          docs/ \
> > +          "${DEPLOY_DESTINATION}${CI_COMMIT_REF_NAME}"
> > +}
> > +
> > +run libcamera_deploy_docs
> > diff --git a/.gitlab-ci/setup-container.sh b/.gitlab-ci/setup-container.sh
> > index c7ca6426c3ab..6c4b439b887b 100755
> > --- a/.gitlab-ci/setup-container.sh
> > +++ b/.gitlab-ci/setup-container.sh
> > @@ -132,6 +132,8 @@ case $FDO_DISTRIBUTION_VERSION in
> >               python3-sphinxcontrib.doxylink
> >               texlive-latex-extra
> >       )
> > +     # Packages required to deploy the documentation
> > +     PKGS_LIBCAMERA_RUNTIME+=( openssh-client rsync )
> >       # Tools requires by the lint jobs.
> >       PKGS_LIBCAMERA_RUNTIME+=( clang-format jq python3-autopep8 reuse shellcheck )
> >       # libclang-rt-dev for the clang ASan runtime.
> > diff --git a/gitlab-ci.yml b/gitlab-ci.yml
> > index 7c5e59de30a1..cef5a16de20a 100644
> > --- a/gitlab-ci.yml
> > +++ b/gitlab-ci.yml
> > @@ -5,6 +5,7 @@ stages:
> >     - build
> >     - lint
> >     - test
> > +  - deploy
> >   
> >   variables:
> >     FDO_UPSTREAM_REPO: 'camera/libcamera'
> > @@ -74,7 +75,7 @@ include:
> >   .libcamera-ci.debian:13:
> >     variables:
> >       FDO_DISTRIBUTION_VERSION: 'trixie'
> > -    FDO_DISTRIBUTION_TAG: '2026-04-24.0'
> > +    FDO_DISTRIBUTION_TAG: '2026-05-06.0'
> >   
> >   .container-debian:
> >     extends:
> > @@ -438,3 +439,29 @@ test-lc-compliance:virtual:
> >     artifacts:
> >       reports:
> >         junit: build/lc-compliance-report.xml
> > +
> > +# ------------------------------------------------------------------------------
> > +# Deploy stage - deploy docs if we are building master or a tag
> > +# ------------------------------------------------------------------------------
> > +
> > +deploy-docs:
> > +  extends:
> > +    - .fdo.distribution-image@debian
> > +    - .libcamera-ci.debian:13
> > +    - .libcamera-ci.scripts
> > +  rules:
> > +    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
> > +    - if: $CI_COMMIT_TAG =~ /^v([0-9]+\.)*[0-9]+$/
> > +  environment: deploy
> 
> production ? To me the name "deploy" does not seem too descriptive,
> and it also matches the name of the stage, which may be confusing.

Actually I liked the fact that it is the same name as the stage. So it
becomes clear on which stage that environment is used. Naming it
production leaves it completely open. As we don't have distinct
staging/production environments, I don't think that really fits our
case.

> 
> > +  stage: deploy
> > +  needs:
> > +    - job: build-docs
> > +  script:
> > +    - |
> > +      mkdir -p ~/.ssh
> > +      chmod 700 ~/.ssh
> > +      echo "${DEPLOY_KNOWN_HOSTS}" > ~/.ssh/known_hosts
> > +      eval $(ssh-agent -s)
> > +      echo "${DEPLOY_SSH_KEY_BASE64}" | base64 -d | ssh-add -
> 
> Why are these all in a single element of the `script` list?
> 

Having a dash in front of every line clutters the code in my opinion, so
I spit it into logical units. What is the benefit in making it
individual items?

Best regards,
Stefan

> 
> > +
> > +    - $CI_PROJECT_DIR/.gitlab-ci/deploy-libcamera-docs.sh
>
Barnabás Pőcze May 12, 2026, 12:29 p.m. UTC | #3
2026. 05. 12. 12:39 keltezéssel, Stefan Klug írta:
> Hi,
> 
> Thank you for the review.
> 
> Quoting Barnabás Pőcze (2026-05-12 11:02:48)
>> Hi
>>
>> 2026. 05. 11. 15:48 keltezéssel, Stefan Klug írta:
>>> Add a build step that deploys the docs to a server. This step
>>> only runs on pushes to master and on tag builds. It requires three
>>> external variables:
>>>
>>> DEPLOY_SSH_KEY_BASE64: base64 encoded private deploy key
>>> DEPLOY_KNOWN_HOSTS: Known hosts containing the destination
>>> DEPLOY_DESTINAION: user@host:/ for the rsync push
>>>
>>> Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
>>>
>>> ---
>>>
>>> Changes in v2:
>>>
>>> - Moved deployment into script
>>> - Improved build rules
>>> - Pipe ssh key into ssh agent instead of creating a file
>>> ---
>>>    .gitlab-ci/deploy-libcamera-docs.sh | 23 +++++++++++++++++++++++
>>>    .gitlab-ci/setup-container.sh       |  2 ++
>>>    gitlab-ci.yml                       | 29 ++++++++++++++++++++++++++++-
>>>    3 files changed, 53 insertions(+), 1 deletion(-)
>>>    create mode 100755 .gitlab-ci/deploy-libcamera-docs.sh
>>>
>>> diff --git a/.gitlab-ci/deploy-libcamera-docs.sh b/.gitlab-ci/deploy-libcamera-docs.sh
>>> new file mode 100755
>>> index 000000000000..a6936728c4a9
>>> --- /dev/null
>>> +++ b/.gitlab-ci/deploy-libcamera-docs.sh
>>> @@ -0,0 +1,23 @@
>>> +#!/bin/bash
>>> +
>>> +# SPDX-License-Identifier: GPL-2.0-or-later
>>> +# SPDX-FileCopyrightText: © 2026 Ideas on Board
>>> +#
>>> +# Deploy libcamera docs
>>> +
>>> +set -e
>>> +
>>> +source "$(dirname "$0")/lib.sh"
>>> +
>>> +libcamera_deploy_docs() {
>>> +     echo "Deploying libcamera docs"
>>> +
>>> +     rsync -rlz --delete --chmod=Do=rx,Fo=r \
>>> +          --exclude .doctrees \
>>> +          --exclude .buildinfo \
>>> +          --exclude objects.inv \
>>> +          docs/ \
>>> +          "${DEPLOY_DESTINATION}${CI_COMMIT_REF_NAME}"
>>> +}
>>> +
>>> +run libcamera_deploy_docs
>>> diff --git a/.gitlab-ci/setup-container.sh b/.gitlab-ci/setup-container.sh
>>> index c7ca6426c3ab..6c4b439b887b 100755
>>> --- a/.gitlab-ci/setup-container.sh
>>> +++ b/.gitlab-ci/setup-container.sh
>>> @@ -132,6 +132,8 @@ case $FDO_DISTRIBUTION_VERSION in
>>>                python3-sphinxcontrib.doxylink
>>>                texlive-latex-extra
>>>        )
>>> +     # Packages required to deploy the documentation
>>> +     PKGS_LIBCAMERA_RUNTIME+=( openssh-client rsync )
>>>        # Tools requires by the lint jobs.
>>>        PKGS_LIBCAMERA_RUNTIME+=( clang-format jq python3-autopep8 reuse shellcheck )
>>>        # libclang-rt-dev for the clang ASan runtime.
>>> diff --git a/gitlab-ci.yml b/gitlab-ci.yml
>>> index 7c5e59de30a1..cef5a16de20a 100644
>>> --- a/gitlab-ci.yml
>>> +++ b/gitlab-ci.yml
>>> @@ -5,6 +5,7 @@ stages:
>>>      - build
>>>      - lint
>>>      - test
>>> +  - deploy
>>>    
>>>    variables:
>>>      FDO_UPSTREAM_REPO: 'camera/libcamera'
>>> @@ -74,7 +75,7 @@ include:
>>>    .libcamera-ci.debian:13:
>>>      variables:
>>>        FDO_DISTRIBUTION_VERSION: 'trixie'
>>> -    FDO_DISTRIBUTION_TAG: '2026-04-24.0'
>>> +    FDO_DISTRIBUTION_TAG: '2026-05-06.0'
>>>    
>>>    .container-debian:
>>>      extends:
>>> @@ -438,3 +439,29 @@ test-lc-compliance:virtual:
>>>      artifacts:
>>>        reports:
>>>          junit: build/lc-compliance-report.xml
>>> +
>>> +# ------------------------------------------------------------------------------
>>> +# Deploy stage - deploy docs if we are building master or a tag
>>> +# ------------------------------------------------------------------------------
>>> +
>>> +deploy-docs:
>>> +  extends:
>>> +    - .fdo.distribution-image@debian
>>> +    - .libcamera-ci.debian:13
>>> +    - .libcamera-ci.scripts
>>> +  rules:
>>> +    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
>>> +    - if: $CI_COMMIT_TAG =~ /^v([0-9]+\.)*[0-9]+$/
>>> +  environment: deploy
>>
>> production ? To me the name "deploy" does not seem too descriptive,
>> and it also matches the name of the stage, which may be confusing.
> 
> Actually I liked the fact that it is the same name as the stage. So it

I see, then let's stick with that.


> becomes clear on which stage that environment is used. Naming it
> production leaves it completely open. As we don't have distinct
> staging/production environments, I don't think that really fits our
> case.

Arguably there is only "production" in this case.


> 
>>
>>> +  stage: deploy
>>> +  needs:
>>> +    - job: build-docs
>>> +  script:
>>> +    - |
>>> +      mkdir -p ~/.ssh
>>> +      chmod 700 ~/.ssh
>>> +      echo "${DEPLOY_KNOWN_HOSTS}" > ~/.ssh/known_hosts
>>> +      eval $(ssh-agent -s)
>>> +      echo "${DEPLOY_SSH_KEY_BASE64}" | base64 -d | ssh-add -
>>
>> Why are these all in a single element of the `script` list?
>>
> 
> Having a dash in front of every line clutters the code in my opinion, so
> I spit it into logical units. What is the benefit in making it
> individual items?

Most other script blocks have one invocation per item, so it looked
a bit out of place, that's why I asked. I don't think there is any
benefit.


> 
> Best regards,
> Stefan
> 
>>
>>> +
>>> +    - $CI_PROJECT_DIR/.gitlab-ci/deploy-libcamera-docs.sh
>>
Laurent Pinchart May 12, 2026, 11:18 p.m. UTC | #4
Hi Stefan,

Thank you for the patch.

On Mon, May 11, 2026 at 03:48:05PM +0200, Stefan Klug wrote:
> Add a build step that deploys the docs to a server. This step
> only runs on pushes to master and on tag builds. It requires three
> external variables:
> 
> DEPLOY_SSH_KEY_BASE64: base64 encoded private deploy key
> DEPLOY_KNOWN_HOSTS: Known hosts containing the destination
> DEPLOY_DESTINAION: user@host:/ for the rsync push
> 
> Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
> 
> ---
> 
> Changes in v2:
> 
> - Moved deployment into script
> - Improved build rules
> - Pipe ssh key into ssh agent instead of creating a file
> ---
>  .gitlab-ci/deploy-libcamera-docs.sh | 23 +++++++++++++++++++++++
>  .gitlab-ci/setup-container.sh       |  2 ++
>  gitlab-ci.yml                       | 29 ++++++++++++++++++++++++++++-
>  3 files changed, 53 insertions(+), 1 deletion(-)
>  create mode 100755 .gitlab-ci/deploy-libcamera-docs.sh
> 
> diff --git a/.gitlab-ci/deploy-libcamera-docs.sh b/.gitlab-ci/deploy-libcamera-docs.sh
> new file mode 100755
> index 000000000000..a6936728c4a9
> --- /dev/null
> +++ b/.gitlab-ci/deploy-libcamera-docs.sh
> @@ -0,0 +1,23 @@
> +#!/bin/bash
> +
> +# SPDX-License-Identifier: GPL-2.0-or-later
> +# SPDX-FileCopyrightText: © 2026 Ideas on Board
> +#
> +# Deploy libcamera docs
> +
> +set -e
> +
> +source "$(dirname "$0")/lib.sh"
> +
> +libcamera_deploy_docs() {
> +	echo "Deploying libcamera docs"
> +
> +	rsync -rlz --delete --chmod=Do=rx,Fo=r \
> +          --exclude .doctrees \
> +          --exclude .buildinfo \
> +          --exclude objects.inv \
> +          docs/ \
> +          "${DEPLOY_DESTINATION}${CI_COMMIT_REF_NAME}"
> +}
> +
> +run libcamera_deploy_docs
> diff --git a/.gitlab-ci/setup-container.sh b/.gitlab-ci/setup-container.sh
> index c7ca6426c3ab..6c4b439b887b 100755
> --- a/.gitlab-ci/setup-container.sh
> +++ b/.gitlab-ci/setup-container.sh
> @@ -132,6 +132,8 @@ case $FDO_DISTRIBUTION_VERSION in
>  		python3-sphinxcontrib.doxylink
>  		texlive-latex-extra
>  	)
> +	# Packages required to deploy the documentation
> +	PKGS_LIBCAMERA_RUNTIME+=( openssh-client rsync )
>  	# Tools requires by the lint jobs.
>  	PKGS_LIBCAMERA_RUNTIME+=( clang-format jq python3-autopep8 reuse shellcheck )
>  	# libclang-rt-dev for the clang ASan runtime.
> diff --git a/gitlab-ci.yml b/gitlab-ci.yml
> index 7c5e59de30a1..cef5a16de20a 100644
> --- a/gitlab-ci.yml
> +++ b/gitlab-ci.yml
> @@ -5,6 +5,7 @@ stages:
>    - build
>    - lint
>    - test
> +  - deploy
>  
>  variables:
>    FDO_UPSTREAM_REPO: 'camera/libcamera'
> @@ -74,7 +75,7 @@ include:
>  .libcamera-ci.debian:13:
>    variables:
>      FDO_DISTRIBUTION_VERSION: 'trixie'
> -    FDO_DISTRIBUTION_TAG: '2026-04-24.0'
> +    FDO_DISTRIBUTION_TAG: '2026-05-06.0'
>  
>  .container-debian:
>    extends:
> @@ -438,3 +439,29 @@ test-lc-compliance:virtual:
>    artifacts:
>      reports:
>        junit: build/lc-compliance-report.xml
> +
> +# ------------------------------------------------------------------------------
> +# Deploy stage - deploy docs if we are building master or a tag
> +# ------------------------------------------------------------------------------
> +
> +deploy-docs:
> +  extends:
> +    - .fdo.distribution-image@debian
> +    - .libcamera-ci.debian:13
> +    - .libcamera-ci.scripts
> +  rules:
> +    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
> +    - if: $CI_COMMIT_TAG =~ /^v([0-9]+\.)*[0-9]+$/
> +  environment: deploy
> +  stage: deploy
> +  needs:
> +    - job: build-docs
> +  script:
> +    - |
> +      mkdir -p ~/.ssh
> +      chmod 700 ~/.ssh
> +      echo "${DEPLOY_KNOWN_HOSTS}" > ~/.ssh/known_hosts
> +      eval $(ssh-agent -s)
> +      echo "${DEPLOY_SSH_KEY_BASE64}" | base64 -d | ssh-add -

I'm still not sure why you don't use a file secret.

Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>

> +
> +    - $CI_PROJECT_DIR/.gitlab-ci/deploy-libcamera-docs.sh

Patch
diff mbox series

diff --git a/.gitlab-ci/deploy-libcamera-docs.sh b/.gitlab-ci/deploy-libcamera-docs.sh
new file mode 100755
index 000000000000..a6936728c4a9
--- /dev/null
+++ b/.gitlab-ci/deploy-libcamera-docs.sh
@@ -0,0 +1,23 @@ 
+#!/bin/bash
+
+# SPDX-License-Identifier: GPL-2.0-or-later
+# SPDX-FileCopyrightText: © 2026 Ideas on Board
+#
+# Deploy libcamera docs
+
+set -e
+
+source "$(dirname "$0")/lib.sh"
+
+libcamera_deploy_docs() {
+	echo "Deploying libcamera docs"
+
+	rsync -rlz --delete --chmod=Do=rx,Fo=r \
+          --exclude .doctrees \
+          --exclude .buildinfo \
+          --exclude objects.inv \
+          docs/ \
+          "${DEPLOY_DESTINATION}${CI_COMMIT_REF_NAME}"
+}
+
+run libcamera_deploy_docs
diff --git a/.gitlab-ci/setup-container.sh b/.gitlab-ci/setup-container.sh
index c7ca6426c3ab..6c4b439b887b 100755
--- a/.gitlab-ci/setup-container.sh
+++ b/.gitlab-ci/setup-container.sh
@@ -132,6 +132,8 @@  case $FDO_DISTRIBUTION_VERSION in
 		python3-sphinxcontrib.doxylink
 		texlive-latex-extra
 	)
+	# Packages required to deploy the documentation
+	PKGS_LIBCAMERA_RUNTIME+=( openssh-client rsync )
 	# Tools requires by the lint jobs.
 	PKGS_LIBCAMERA_RUNTIME+=( clang-format jq python3-autopep8 reuse shellcheck )
 	# libclang-rt-dev for the clang ASan runtime.
diff --git a/gitlab-ci.yml b/gitlab-ci.yml
index 7c5e59de30a1..cef5a16de20a 100644
--- a/gitlab-ci.yml
+++ b/gitlab-ci.yml
@@ -5,6 +5,7 @@  stages:
   - build
   - lint
   - test
+  - deploy
 
 variables:
   FDO_UPSTREAM_REPO: 'camera/libcamera'
@@ -74,7 +75,7 @@  include:
 .libcamera-ci.debian:13:
   variables:
     FDO_DISTRIBUTION_VERSION: 'trixie'
-    FDO_DISTRIBUTION_TAG: '2026-04-24.0'
+    FDO_DISTRIBUTION_TAG: '2026-05-06.0'
 
 .container-debian:
   extends:
@@ -438,3 +439,29 @@  test-lc-compliance:virtual:
   artifacts:
     reports:
       junit: build/lc-compliance-report.xml
+
+# ------------------------------------------------------------------------------
+# Deploy stage - deploy docs if we are building master or a tag
+# ------------------------------------------------------------------------------
+
+deploy-docs:
+  extends:
+    - .fdo.distribution-image@debian
+    - .libcamera-ci.debian:13
+    - .libcamera-ci.scripts
+  rules:
+    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
+    - if: $CI_COMMIT_TAG =~ /^v([0-9]+\.)*[0-9]+$/
+  environment: deploy
+  stage: deploy
+  needs:
+    - job: build-docs
+  script:
+    - |
+      mkdir -p ~/.ssh
+      chmod 700 ~/.ssh
+      echo "${DEPLOY_KNOWN_HOSTS}" > ~/.ssh/known_hosts
+      eval $(ssh-agent -s)
+      echo "${DEPLOY_SSH_KEY_BASE64}" | base64 -d | ssh-add -
+
+    - $CI_PROJECT_DIR/.gitlab-ci/deploy-libcamera-docs.sh