Blog

Pushing container images in Forgejo actions

Pushing container images

With GitHub actions most people use docker push to push their images to a registry.

With Forgejo actions, that probably won't work. because of docker-in-docker. Instead, you can use the skopeo to push your images to a registry.

To Setup CONTAINER_TOKEN:

  • create a token https://git.example.com/user/settings/applications
  • then add the token to your secrets https://forgejo.example.com/user/settings/actions/secrets

Note:

Forgejo create a Automatic token with each workflow run.

But you can't use it to push images to a registry.

name: docker

on:
  push:
    branches: [main]

env:
  REGISTRY: git.nexveridian.com
  IMAGE_NAME: ${{ github.repository }}
  NIX_CONFIG: "experimental-features = nix-command flakes"
  CONTAINER_TOKEN: ${{ secrets.CONTAINER_REGISTRY_TOKEN }}

jobs:
  build:
    runs-on: nix
    permissions:
      contents: read
      packages: write
      id-token: write

    steps:
      - run: nix run -I nixpkgs=channel:nixos-unstable nixpkgs#attic-client login nex https://nix.example.com ${{ secrets.ATTIC_TOKEN }} || true
      - run: nix run -I nixpkgs=channel:nixos-unstable nixpkgs#attic-client cache create <cache name> || true
      - run: nix run -I nixpkgs=channel:nixos-unstable nixpkgs#attic-client cache configure <cache name> -- --priority 30 || true
      - run: nix run -I nixpkgs=channel:nixos-unstable nixpkgs#attic-client use <cache name> || true

      - name: Install Node.js
        run: |
          mkdir -p ~/.local/bin
          nix build -I nixpkgs=channel:nixos-unstable nixpkgs#nodejs_24 -o ~/.local/nodejs
          ln -sf ~/.local/nodejs/bin/node ~/.local/bin/node
          ln -sf ~/.local/nodejs/bin/npm ~/.local/bin/npm
          echo "$HOME/.local/bin" >> $GITHUB_PATH

      - uses: actions/checkout@v4

      - name: Install skopeo
        run: |
          mkdir -p ~/.local/bin
          nix build -I nixpkgs=channel:nixos-unstable nixpkgs#skopeo -o ~/.local/skopeo
          ln -sf ~/.local/skopeo/bin/skopeo ~/.local/bin/skopeo
          echo "$HOME/.local/bin" >> $GITHUB_PATH

      - name: Build Nix package
        run: nix build .#my-docker

      - name: Prepare repository variables
        run: |
          echo "REPO=${GITHUB_REPOSITORY,,}" >> ${GITHUB_ENV}
          echo "OWNER=${GITHUB_REPOSITORY_OWNER,,}" >> ${GITHUB_ENV}
          # Extract just the repository name (everything after the last slash)
          REPO_NAME=${GITHUB_REPOSITORY##*/}
          echo "IMAGE_NAME=${REPO_NAME,,}" >> ${GITHUB_ENV}

      - name: Setup skopeo policy and push image
        run: |
          # configure container policy to accept insecure registry
          mkdir -p ~/.config/containers
          cat > ~/.config/containers/policy.json <<EOF
          {
            "default": [{"type":"insecureAcceptAnything"}]
          }
          EOF

          # ensure all required directories exist with proper permissions
          mkdir -p /tmp/skopeo /var/tmp ~/.local/share/containers
          chmod 755 /tmp/skopeo /var/tmp || true

          # set multiple environment variables for skopeo temporary directories
          export TMPDIR=/tmp/skopeo
          export TMP=/tmp/skopeo
          export TEMP=/tmp/skopeo
          export XDG_RUNTIME_DIR=/tmp/skopeo

          # The Nix build creates a compressed tar.gz file, we need to extract it first
          cd /tmp/skopeo
          cp ${GITHUB_WORKSPACE}/result ./docker-image.tar.gz
          gunzip docker-image.tar.gz

          # Create authentication file for skopeo
          mkdir -p ~/.docker
          cat > ~/.docker/config.json <<EOF
          {
            "auths": {
              "${{ env.REGISTRY }}": {
                "auth": "$(echo -n "${{ github.actor }}:${{ env.CONTAINER_TOKEN }}" | base64 -w 0)"
              }
            }
          }
          EOF

          # Also create auth for containers directory
          mkdir -p ~/.config/containers
          cp ~/.docker/config.json ~/.config/containers/auth.json

          # Test registry connectivity
          skopeo login --username "${{ github.actor }}" --password "${{ env.CONTAINER_TOKEN }}" "${{ env.REGISTRY }}"

          # Push image using Personal Access Token
          skopeo copy \
            --dest-tls-verify=false \
            --tmpdir /tmp/skopeo \
            --dest-creds "${{ github.actor }}:${{ env.CONTAINER_TOKEN }}" \
            docker-archive:/tmp/skopeo/docker-image.tar \
            docker://${{ env.REGISTRY }}/${{ env.OWNER }}/${{ env.IMAGE_NAME }}:latest

      - name: Push to attic
        if: always()
        run: |
          valid_paths=""
          for path in /nix/store/*/; do
            if nix path-info "$path" >/dev/null 2>&1; then
              valid_paths="$valid_paths $path"
            fi
          done

          if [ -n "$valid_paths" ]; then
            for i in {1..10}; do
              nix run -I nixpkgs=channel:nixos-unstable nixpkgs#attic-client push <cache name> $valid_paths && break || [ $i -eq 5 ] || sleep 5
            done
          fi

Swapping from GitHub to Forgejo actions, with `Nix` based actions

Forgejo Actions

Most actions are the mostly the same, but some things, like conncrurrency groups don't work.

name: nix

on:
  pull_request:
    branches: [main]
  push:
  schedule:
    - cron: 0 0 * * 1

env:
  CARGO_TERM_COLOR: always
  NIX_CONFIG: "experimental-features = nix-command flakes"

jobs:
  check-dependencies:
    name: check-dependencies
    # Change to a valid Forgejo runner image
    # runs-on: ubuntu-latest
    runs-on: nix
    permissions:
      contents: read
      id-token: write
      actions: write

    steps:
      - run: nix run -I nixpkgs=channel:nixos-unstable nixpkgs#attic-client login <pick a name for server> https://nix.example.com ${{ secrets.ATTIC_TOKEN }} || true
      - run: nix run -I nixpkgs=channel:nixos-unstable nixpkgs#attic-client cache create <cache name> || true
      - run: nix run -I nixpkgs=channel:nixos-unstable nixpkgs#attic-client cache configure <cache name> -- --priority 30 || true
      - run: nix run -I nixpkgs=channel:nixos-unstable nixpkgs#attic-client use <cache name> || true

      # Install and configure Node.js, since it's not setup in the default nix
      - name: Install Node.js
        run: |
          mkdir -p ~/.local/bin
          nix build -I nixpkgs=channel:nixos-unstable nixpkgs#nodejs_24 -o ~/.local/nodejs
          ln -sf ~/.local/nodejs/bin/node ~/.local/bin/node
          ln -sf ~/.local/nodejs/bin/npm ~/.local/bin/npm
          echo "$HOME/.local/bin" >> $GITHUB_PATH

      - uses: actions/checkout@v5

      - run: nix build -I nixpkgs=channel:nixos-unstable nixpkgs#nix-fast-build

      - name: check
        run: |
          nix run -I nixpkgs=channel:nixos-unstable nixpkgs#nix-fast-build -- --no-nom --skip-cached

      - name: Push to attic
        if: always()
        run: |
          valid_paths=""
          for path in /nix/store/*/; do
            if nix path-info "$path" >/dev/null 2>&1; then
              valid_paths="$valid_paths $path"
            fi
          done

          if [ -n "$valid_paths" ]; then
            for i in {1..10}; do
              nix run -I nixpkgs=channel:nixos-unstable nixpkgs#attic-client push nextrack $valid_paths && break || [ $i -eq 5 ] || sleep 5
            done
          fi

Forgejo Actions

# .runner
{
  "WARNING": "This file is automatically generated by act-runner. Do not edit it manually unless you know what you are doing. Removing this file will cause act runner to re-register as a new runner.",
  "id": 1,
  "uuid": "****",
  "name": "<runner name>",
  "token": "****",
  "address": "https://git.example.com",
  "labels": [
    "bookworm:docker://node:24-bookworm",
    "nix-base:docker://docker.nix-community.org/nixpkgs/nix-unstable:latest",
    "nix:docker://git.nexveridian.com/nexveridian/action-attic:latest"
  ]
}

Available runner images

  • bookworm: closest to GitHub actions
  • nix-base: for bootstrapping
  • nix: custom image with packages pre installed

Creating custom runner images

git clone ssh://[email protected]:222/NexVeridian/docker-nixpkgs.git

Create a copy of images/action-attic

{
  docker-nixpkgs,
  pkgs,
  attic-client,
  nodejs_24,
  nix-fast-build,
  # add more packages here
}:
(docker-nixpkgs.nix.override {
  nix = pkgs.nixVersions.latest;

  extraContents = [
    attic-client
    nodejs_24
    nix-fast-build
    # and the corresponding packages here
  ];
}).overrideAttrs
  (prev: {
    meta = (prev.meta or { }) // {
      description = "Forgejo action image, with Nix and Attic client";
    };
  })

Edit folder name in .forgejo/workflows/nix.yaml

- name: Build Nix package
  run: nix-build -A action-attic

Pushing docker container images

With GitHub actions most people use docker push to push their images to a registry.

With Forgejo actions, that probably won't work. because of docker-in-docker. Instead, you can use the skopeo to push your images to a registry.

To Setup CONTAINER_TOKEN:

  • create a token https://git.example.com/user/settings/applications
  • then add the token to your secrets https://forgejo.example.com/user/settings/actions/secrets
name: docker

on:
  push:
    branches: [main]

env:
  REGISTRY: git.nexveridian.com
  IMAGE_NAME: ${{ github.repository }}
  NIX_CONFIG: "experimental-features = nix-command flakes"
  CONTAINER_TOKEN: ${{ secrets.CONTAINER_REGISTRY_TOKEN }}

jobs:
  build:
    runs-on: nix
    permissions:
      contents: read
      packages: write
      id-token: write

    steps:
      - run: nix run -I nixpkgs=channel:nixos-unstable nixpkgs#attic-client login nex https://nix.example.com ${{ secrets.ATTIC_TOKEN }} || true
      - run: nix run -I nixpkgs=channel:nixos-unstable nixpkgs#attic-client cache create <cache name> || true
      - run: nix run -I nixpkgs=channel:nixos-unstable nixpkgs#attic-client cache configure <cache name> -- --priority 30 || true
      - run: nix run -I nixpkgs=channel:nixos-unstable nixpkgs#attic-client use <cache name> || true

      - name: Install Node.js
        run: |
          mkdir -p ~/.local/bin
          nix build -I nixpkgs=channel:nixos-unstable nixpkgs#nodejs_24 -o ~/.local/nodejs
          ln -sf ~/.local/nodejs/bin/node ~/.local/bin/node
          ln -sf ~/.local/nodejs/bin/npm ~/.local/bin/npm
          echo "$HOME/.local/bin" >> $GITHUB_PATH

      - uses: actions/checkout@v4

      - name: Install skopeo
        run: |
          mkdir -p ~/.local/bin
          nix build -I nixpkgs=channel:nixos-unstable nixpkgs#skopeo -o ~/.local/skopeo
          ln -sf ~/.local/skopeo/bin/skopeo ~/.local/bin/skopeo
          echo "$HOME/.local/bin" >> $GITHUB_PATH

      - name: Build Nix package
        run: nix build .#my-docker

      - name: Prepare repository variables
        run: |
          echo "REPO=${GITHUB_REPOSITORY,,}" >> ${GITHUB_ENV}
          echo "OWNER=${GITHUB_REPOSITORY_OWNER,,}" >> ${GITHUB_ENV}
          # Extract just the repository name (everything after the last slash)
          REPO_NAME=${GITHUB_REPOSITORY##*/}
          echo "IMAGE_NAME=${REPO_NAME,,}" >> ${GITHUB_ENV}

      - name: Setup skopeo policy and push image
        run: |
          # configure container policy to accept insecure registry
          mkdir -p ~/.config/containers
          cat > ~/.config/containers/policy.json <<EOF
          {
            "default": [{"type":"insecureAcceptAnything"}]
          }
          EOF

          # ensure all required directories exist with proper permissions
          mkdir -p /tmp/skopeo /var/tmp ~/.local/share/containers
          chmod 755 /tmp/skopeo /var/tmp || true

          # set multiple environment variables for skopeo temporary directories
          export TMPDIR=/tmp/skopeo
          export TMP=/tmp/skopeo
          export TEMP=/tmp/skopeo
          export XDG_RUNTIME_DIR=/tmp/skopeo

          # The Nix build creates a compressed tar.gz file, we need to extract it first
          cd /tmp/skopeo
          cp ${GITHUB_WORKSPACE}/result ./docker-image.tar.gz
          gunzip docker-image.tar.gz

          # Create authentication file for skopeo
          mkdir -p ~/.docker
          cat > ~/.docker/config.json <<EOF
          {
            "auths": {
              "${{ env.REGISTRY }}": {
                "auth": "$(echo -n "${{ github.actor }}:${{ env.CONTAINER_TOKEN }}" | base64 -w 0)"
              }
            }
          }
          EOF

          # Also create auth for containers directory
          mkdir -p ~/.config/containers
          cp ~/.docker/config.json ~/.config/containers/auth.json

          # Test registry connectivity
          skopeo login --username "${{ github.actor }}" --password "${{ env.CONTAINER_TOKEN }}" "${{ env.REGISTRY }}"

          # Push image using Personal Access Token
          skopeo copy \
            --dest-tls-verify=false \
            --tmpdir /tmp/skopeo \
            --dest-creds "${{ github.actor }}:${{ env.CONTAINER_TOKEN }}" \
            docker-archive:/tmp/skopeo/docker-image.tar \
            docker://${{ env.REGISTRY }}/${{ env.OWNER }}/${{ env.IMAGE_NAME }}:latest

      - name: Push to attic
        if: always()
        run: |
          valid_paths=""
          for path in /nix/store/*/; do
            if nix path-info "$path" >/dev/null 2>&1; then
              valid_paths="$valid_paths $path"
            fi
          done

          if [ -n "$valid_paths" ]; then
            for i in {1..10}; do
              nix run -I nixpkgs=channel:nixos-unstable nixpkgs#attic-client push <cache name> $valid_paths && break || [ $i -eq 5 ] || sleep 5
            done
          fi

Creating custom `Nix` Forgejo actions images

Creating custom runner images

git clone ssh://[email protected]:222/NexVeridian/docker-nixpkgs.git

Create a copy of images/action-attic

{
  docker-nixpkgs,
  pkgs,
  attic-client,
  nodejs_24,
  nix-fast-build,
  # add more packages here
}:
(docker-nixpkgs.nix.override {
  nix = pkgs.nixVersions.latest;

  extraContents = [
    attic-client
    nodejs_24
    nix-fast-build
    # and the corresponding packages here
  ];
}).overrideAttrs
  (prev: {
    meta = (prev.meta or { }) // {
      description = "Forgejo action image, with Nix and Attic client";
    };
  })

Edit folder name in .forgejo/workflows/nix.yaml

- name: Build Nix package
  run: nix-build -A action-attic