Roger Clotet Solé

Hello, World!

I'm a dad and a software craftsman based in Girona
I build stuff for the web and distributed systems
I love learning, photography, videogames, and driving

Latest posts

A GitLab CI workflow for an Android game made with Godot

Did I need to automate everything related to building and releasing my game Drift Survivors? Of course not. I'm the only developer and I could simply do builds manually and upload them to the Google Play Console as I did with other small games I created.

The thing is, the more you automate, the easier it is to develop and, more importantly, to come back after a few months to add a new feature or fix some bug. Otherwise, it's likely you don't remember how the build worked, what keystore or password you need, or where exactly in the Google Play Console you uploaded versions. To me, not needing to remember a thing is saving effort you can be spending doing something more productive.


My current continuous integration pipeline is something like this:

Continuous integration worflow by stage

Or if we look at it by dependencies:

Continuous integration workflow by dependencies

I'll explain and copy the configuration for each state. For now, every job is only run when a tag is created (only: - tags), since it's when I want to create a new release, enabling me to just push commits to the main branch when I want. Since I'm usually the only developer in the project, it makes sense to skip creating new branches for most small changes.

Build stage

  image: rogerclotet/godot-ci:4.0.2
    key: import-assets
      - .godot/imported/
    - echo $SECRET_RELEASE_KEYSTORE_BASE64 | base64 --decode > /root/release.keystore
    - echo "textures/vram_compression/import_etc2_astc=true" >> project.godot
    - echo "Opening editor to import assets..."
    - godot -v -e --quit --headless 2>&1 | grep -v "StringName"
    - sed 's@keystore/release=".*"@keystore/release="'/root/release.keystore'"@g' -i export_presets.cfg
    - sed 's@keystore/release_user=".*"@keystore/release_user="'$SECRET_RELEASE_KEYSTORE_USER'"@g' -i export_presets.cfg
    - sed 's@keystore/release_password=".*"@keystore/release_password="'$SECRET_RELEASE_KEYSTORE_PASSWORD'"@g' -i export_presets.cfg
    - sed 's@version/code=.*@version/code='$(git show -s --format=%ct $CI_COMMIT_TAG)'@g' -i export_presets.cfg
    - sed 's@version/name=".*"@version/name="'$CI_COMMIT_TAG'"@g' -i export_presets.cfg

  stage: build
  extends: .godot-android
    - godot -q --headless --export-release "Android AAB" $AAB_FILE 2>&1 | grep -v "StringName"
    - if ! [[ -f "$AAB_FILE" ]]; then exit 1; fi
    - tags
      - $AAB_FILE

  stage: build
  extends: .godot-android
    - godot -q --headless --export-release "Android APK" $APK_FILE 2>&1 | grep -v "StringName"
    - if ! [[ -f "$APK_FILE" ]]; then exit 1; fi
    - tags
      - $APK_FILE
  • .godot-android is the shared configuration for the 2 jobs in this stage. It defines the image used (a fork of https://github.com/aBARICHELLO/godot-ci/ to be able to use Godot 4 until the new fixes are merged), the cache directories for imported assets, and some configuration steps for both the Godot editor and the project settings, to prepare for the build itself. It also runs the Godot editor in headless mode and quitting right after that, just to make sure assets are imported and the project is properly initialized.
  • android_aab extends this, builds the AAB export configuration and makes sure the file has been generated correctly, and marks it as an artifact to be exported. An AAB file is what we need to upload the game to the Play Store.
  • android_apk is exactly like android_aab but it creates an APK file, which can be installed directly to any Android device. I used two separate jobs for this to export the two files separately, to make it easier to make them downloadable, as we'll see below.

Publish Stage

  stage: publish
  needs: [android_aab, android_apk]
  image: curlimages/curl:latest
    - 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file $AAB_FILE "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/drift-survivors/${CI_COMMIT_TAG}/${AAB_FILE}"'
    - 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file $APK_FILE "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/drift-survivors/${CI_COMMIT_TAG}/${APK_FILE}"'
    - tags

  stage: publish
  needs: [publish_packages]
  image: registry.gitlab.com/gitlab-org/release-cli:latest
    - tags
    - apk add git
    - echo "Creating release $CI_COMMIT_TAG..."
    tag_name: $CI_COMMIT_TAG
    description: |
      $(git log $(git describe --abbrev=0 --tags --exclude=$CI_COMMIT_TAG)..$CI_COMMIT_TAG --oneline --no-decorate --reverse | sed "s/^[^ ]* /- /g")
        - name: AAB
          url: ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/drift-survivors/${CI_COMMIT_TAG}/${AAB_FILE}
          link_type: package
        - name: APK
          url: ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/drift-survivors/${CI_COMMIT_TAG}/${APK_FILE}
          link_type: package

  image: cijumbo/fastlane
    - bundle update fastlane
    - curl -s https://gitlab.com/gitlab-org/incubation-engineering/mobile-devops/download-secure-files/-/raw/main/installer | bash
    - android_aab

  stage: publish
  needs: [android_aab]
  extends: .fastlane
  script: bundle exec fastlane supply --track internal --aab $EXPORT_NAME.$CI_COMMIT_TAG.aab
    - tags
  • publish_packages publishes the two exported files (AAB and APK) as generic packages to GitLab. This allows us to have a permanent link to the files in the release, since linking to build artifacts is not recommended.
  • release creates the GitLab release, automatically gathering changes descriptions from the commits since the last release, and linking the packages from the previous job.
  • .fastlane is a shared job configuration used by deploy_to_internal and promote_to_production, which we'll see later. It uses an image that includes fastlane, a tool to automate releases and changes to apps in the Play Store and the App Store. It also downloads the needed secure files from GitLab, namely the keystore to sign the app we want to deploy.
  • deploy_to_internal uses fastlane to upload the AAB package to the internal track in the Play Store, used for internal testing with limited approved users. After I test everything looks okay when I update the game on my phone, we can go to the final stage:

Production stage

  stage: production
  needs: [deploy_to_internal]
  extends: .fastlane
  when: manual
  script: bundle exec fastlane supply --track internal --track_promote_to production --version_code $(git show -s --format=%ct $CI_COMMIT_TAG)
  dependencies: []
    - tags
  • promote_to_production promotes the version we tested in the internal track to the production1 track, which will be reviewed by Google and made available to all users after that. This step must be triggered manually, in case the internal version was not suitable for production.

We could add some intermediate steps, like going th the alpha or beta tracks before that, to allow some users to start testing the new update before going to production, but for now that's more than enough.


Automating your releases isn't free, it took a lot of trial and error, but I think it has many benefits. It also generates some nice releases in the repository, where your users can download the exported packages even if they don't use the Play Store, or just want to test an old version of the game. The releases look like this:

Release example

This set up was created for my game Drift Survivors, which you can already get for free in the Play Store, and you can see a more updated version of the configuration here.

See all posts in the blog

Work Experience


TravelPerk Logo

Senior Software Engineer

June 2019 - March 2020

I worked in TravelPerk as a full-stack senior software engineer.

Small multidisciplinary squads with rapid development cycles trying to improve and modernise business travel. Fast development cycles with continuous integration and deployment.

Used React in frontend and Python with Django and Tornado in backend. I spent most of my time in the accommodations squad, and I was also involved in engineering recruitment, and frontend/backend guilds.

I learned a lot and was enjoying working in TravelPerk and being part of a great team, left to improve work-life balance since my son was about to be born and we didn't have remote options at the time.

Social Point Logo
Social Point

Backend Engineer / Senior Backend Engineer

June 2012 - May 2019

I worked developing the server side of highly successful mobile games, with millions of daily active users. During 5 years in a 5 people team and for a year in a different project in a 2-person team.

Encountered many challenges, mostly related to concurrency and high availability. Used mostly PHP (with Symfony) and Go, but also many more technologies for specific problems, like Redis, SQS, DynamoDB, CloudWatch and ElasticSearch. Learned the importance of good design and maintainability -with games like Dragon City with 5+ years of active development- and test-driven development. Experience in maintaining old code, improving it and making it future-proof.

Personal projects

These are some of my personal projects. Most things I develop as side projects don't end up anywhere and only serve as learning experiences, which are very valuable, but these are the ones I think are worth sharing the most

Drift Survivors - 2023

A mobile game inspired in Vampire Survivors, where you drive a car drifting around a desert, running over enemies, and shooting them with several weapons, in order to survive as long as possible while waves of stronger and stronger enemies come to you.

This game was developed using Godot and using custom pixel art. The architecture of the project is built with expandability in mind, and it's easy to add new weapons and upgrades to the game.

You can download and play the game on Android from the Play Store.

Source code

Drift Survivors

Project Minesweeper - 2021

This is a multiplayer minesweeper game I built to learn about Socket.IO and NodeJS, inspired by Minesweeper Flags.

It uses real-time communication with server state persistence. It has a chat, editable user profiles, configurable web and desktop notifications, and a game list with board preview to manage multiple concurrent games.

On the technical side, I used ReactJS, the previously mentioned Socket.IO, MaterialUI, tailwindcss, and MongoDB.

You can read more about it in the blog: Project Minesweeper Part 1: Introduction and Tech

Source code

Project Minesweeper

Blocs! - 2020

A small puzzle game for mobile. The first game I published on the Google Play Store.

The gameplay is inspired by other puzzle games I tried before, but were full of ads and in-app purchases. I started learning game development some time before, took part in a game jam, and wanted to make a simple game to "test my skills", and this seemed like a good way of doing it.

It was made with Unity, art was created with Aseprite, and has local saves for high scores, last games and current game state, localization in Catalan, English, and Spanish, and different color schemes to choose from.

You can install and play the game for free on Android, or play now from mobile or desktop on https://blocs.clotet.dev.

Source code


Amic Invisible webapp - 2018

A webapp to manage Secret Santa groups with friends and family, built as a prototype with plans to rewrite it in a better way in the future.

It allows you to create groups, invite people using a link or using the share dialog in your phone, decide randomly who is each other's secret Santa, and share a "wish list" to help her or him get ideas for your gift.

Amic Invisible webapp

Ja Arribarem Club website - 2006

A website for a popular walk event I've built, iterated upon, and maintained since 2006.

It started as a static site to provide basic information of the current year's event, and since then I've rewritten it a couple of times to what it is today: s Symfony application with an administration zone to edit and add new contents every year and require minimal maintenance from my side.

Source code

Ja Arribarem Club website

That's it!

I'm always working in side projects and willing to collaborate.

Feel free to get in touch!