Git push from inside a gitlab-runner

It seems like something most people would want to do. You have something in your pipeline changing the project and then you want to push. However whenever I attempt to do a git push from my gitlab runner I get a 403 permission denied. Most things I find on the internet say it is because of it attempting to use the CI token. I have the SSH keys on the machine the runner is running on.

How do people do pushes from a gitlab runner?


Use a git credential helper to push to the repo. Take a look at the the “badge” stage of the following gitlab-ci.yaml for an example:

We did this by adding a before_script task where we install SSH & Git and set the Git credentials.

The var SSH_PRIVATE_KEY_TOOLKIT in the example below is a Deploy Key generated in Gitlab by going to Settings > Repository > Deploy Keys, Make sure to enable write access by editing the deploy key after enabling it.

    - apt-get update -y && apt-get install -yqqf openssh-client git unzip sshpass rsync --fix-missing
    - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client git -y )'
    - eval $(ssh-agent -s)
    - echo "$SSH_PRIVATE_KEY_TOOLKIT" | tr -d '\r' | ssh-add - > /dev/null

    - mkdir -p ~/.ssh
    - chmod 700 ~/.ssh

    - ssh-keyscan $GITLAB_URL >> ~/.ssh/known_hosts
    - chmod 644 ~/.ssh/known_hosts

    - git config --global ""
    - git config --global "Gitlab Runner"

Then in your script you can do this:

    # Pull repo
    - echo "Pulling external repo into build"
    - ssh git@$GITLAB_URL
    - git clone git@$GITLAB_URL:$EXTERNAL_REPO_URL filelocation/we/expect/the/files
    # Do something after pulling your repo
    # Push repo changes into current repo
    - git remote rm origin && git remote add origin "git@$GITLAB_URL:${CI_PROJECT_PATH}.git"
    - git add filelocation/we/expect/the/files another/location/can/be/defined
    - git commit -m "Commit message here :)" || echo "No changes, nothing to commit!"
    - git push origin HEAD:$CI_COMMIT_REF_NAME

Hope this helps you out.


My solution wasn’t as complicated, maybe because I am relying on the gitlab-runner machine always having the correct ssh keys set up. I have posted below for anyone else

- 'standard-version --releaseCommitMessageFormat "chore(release): {{currentTag}}"'
- git remote show origin
- git remote set-url --push origin git@gitlab:$CI_PROJECT_PATH
- git remote show origin
- git push --follow-tags origin HEAD:$CI_COMMIT_REF_NAME
- npm publish

This changes the remote taking out the whole CI token stuff


In case others are wondering, I found a nice guide to set this up with shared GitLab runners and compiled it into another answer here.

In short:

  stage: deploy
    - 'which ssh-agent || ( apt-get update -qy && apt-get install openssh-client -qqy )'
    - eval `ssh-agent -s`
    - echo "${SSH_PRIVATE_KEY}" | tr -d '\r' | ssh-add - > /dev/null # add ssh ke
    - mkdir -p ~/.ssh
    - chmod 700 ~/.ssh
    - echo "$SSH_PUBLIC_KEY" >> ~/.ssh/
    - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
    - git config --global "${CI_EMAIL}"
    - git config --global "${CI_USERNAME}"
    - git add -f *.pdf # Force add PDF since we .gitignored it
    - git commit -m "Compiled PDF from $CI_COMMIT_SHORT_SHA [skip ci]" || echo "No changes, nothing to commit!"
    - git remote rm origin && git remote add origin$CI_PROJECT_PATH.git
    - git push origin HEAD:$CI_COMMIT_REF_NAME # Pushes to the same branch as the trigger

I’ve been searching for an easy solution for this for a long time and finally decided to build my own. As most of my projects use JavaScript, I wrote this small NPM package, push-to-repo, that allows you to push any changed file back to the repo with one command. For example (tweets-to-json fetches tweets and saves them in a json file):

    - schedules
    - yarn --frozen-lockfile
    - yarn tweets-to-json --fail-when-zero && yarn push-to-repo -f tweets.json
  allow_failure: true

I use this with a scheduled pipeline that fetches my tweets as a JSON file to feed to a static site builder.

Only thing you need to configure this is a valid access token in your project CI settings. The package is open source and available here:

I have a question about the deploy key, and your solution is the nearest to mine. As I understand correctly, you use the variable SSH_PRIVATE_KEY_TOOLKIT as the private key to connect to the repository? From reading the manuals from gitlab I was thinking the deploy key needs to be the public key and I put the private key as variable inside my repo CI Variables.

So if I am wrong an the deploy key needs to be the private key, where do I save the public key? Do I need a additional Gitlab-Runner-User who has saved his public key in his settings?

Nope simply create a SSH Key pair, you can do this on your computer ( by using Putty for example ).
Then define the public key in the deploy key section within a project by visiting Settings > Repository > Deploy Keys, Make sure you hit “Write access allowed”.
Then define your Private key in the variable SSH_PRIVATE_KEY_TOOLKIT.

Note: Best would be to add this variable on group level so that you won’t need to redefine it for new projects you create. ( Only if this applies to you workflow, for us this made sense since we want to pull the repo to each repo in our project group )

Then make sure this deploy key is enabled in both the repositories and has “Write access allowed” for the repo where the files should be pushed to. “Note when enabling the deploy key in a new repo the Write access is unchecked by default, so enable it and then edit and allow write access”.

Hopefully this clear things up for you, if there is still anything unclear let me know maybe I can help out.

Thats makes everything a bit more clear. So regarding to you post I used it in our CI. My problem is, that my scripts runs fine until I hit the point, where I want to push something back to the repo.

I have an opend stackoverflow question with all the additional Informations. I tested it with a windows gitlab-runner and a linux gitlab-runner, both struggeling to push, based on restrictions. If you want to see the linux code feel free to ask.

I took a quick look at your stackoverflow question and was wondering if you created a SSH Key without a password ? Since the error thrown is also the error that will be thrown when your SSH Key is password protected but no password was supplied.

We’ve actually ran into this issue as well and fixed it by creating a new key that was not password protected.

Might it be a problem that I created the keys with tapping return twice for password while keygeneration and not prohibit passwordphrase with a flag?

I’m not sure if that might cause this error, but sounds like it’s worth a try to generate a new key pair to see if that was actually the cause.

I finally could figure out, that atleast some part for ssh and gitlab is working. I figured out that I have some problem to use my WSL to git clone (or something else git-server related) with just my openssh config. But at least the core.sshCommand get me working. Sadly the GIT_SSH_COMMAND env variable doesn’t do the job for the git-runner. At a last glimps from my side, could it be that the gitlabrunner-user isn’t allowed to access the repository because it is private?

Does pushing a new commit from within the pipeline cause another pipeline to run?


I had to do something very similar, but the CI_EMAIL and CI_USERNAME did not exist (using SaaS) - I had to instead specify:

    - git config --global "${GITLAB_USER_EMAIL}"
    - git config --global "${GITLAB_USER_NAME}"
1 Like

Hi @jrwang ,
I’m a bit of a newbie so apologize if the questions a basic.
I’m trying to apply your solution but running into some syntax errors.
Initially I got these errors:

At C:\Windows\TEMP\build_script039793081\script.ps1:285 char:17
+ which ssh-agent || ( apt-get update -qy && apt-get install openssh-cl ...
+                 ~~
The token '||' is not a valid statement separator in this version.

I figured it’s bc I’m using powershell so I should use -or and -and instead of || and && (is that correct?).

Now I’m getting error about the last line in the before_script, but I’m not sure how to adjust that line to powershell (I don’t really understand that line :thinking:)

At C:\Windows\TEMP\build_script991080655\script.ps1:309 char:2
+ [[ -f /.dockerenv ]] -and echo -e "Host *\n\tStrictHostKeyChecking no ...
+  ~
Missing type name after '['.
    + CategoryInfo          : ParserError: (:) [], ParseException
    + FullyQualifiedErrorId : MissingTypename

Will appreciate any help…