Repository, Default Branch, Branch Protection, Tags
Before the first line of .gitlab-ci.yml is written, the repository, default branch, branch protection and protected tags need to be configured correctly. These settings form the organizational framework that keeps technically sound pipelines from being undermined by poor governance.
Table of Contents
- 1. Why project setup is the first step
- 2. Repository structure for Magento
- 3. Setting up .gitignore for Magento correctly
- 4. Default branch and settings
- 5. Branch protection: main and release/*
- 6. Merge requests and approval rules
- 7. Protected tags for release approvals
- 8. Setting up deploy keys and SSH access
- 9. Setup variants compared
- 10. Summary
- 11. FAQ
1. Why project setup is the first step
GitLab project setup is not a formality you can catch up on later. Teams that jump straight into the pipeline and skip project setup usually only notice something is missing once the first incident happens: a developer pushes directly to main and accidentally triggers a deployment. A production secret is too widely visible and gets used in a feature branch job. A release tag is created by a developer without maintainer rights and triggers an unreviewed production deployment. These scenarios are avoidable, but only if the project was set up correctly before the first pipeline run.
For Magento projects this is especially critical. A Magento shop contains sensitive configuration data, Composer credentials for Adobe Commerce, and SSH keys for deployment servers. This data must never end up in the repository and needs to be managed in CI/CD variables with correct scopes and flags. Anyone who does not set up the repository from day one with a correct .gitignore and structured branch rules creates technical debt that is very costly to fix later.
The good news: correctly setting up a GitLab project for Magento takes about two hours and can be documented once. After that it serves as a template for every future Magento project in the same organization. The following sections walk through each step, from the repository structure through branch protection and protected tags to deploy keys and SSH access.
2. Repository structure for Magento
The decision about what belongs in the repository and what does not has a direct impact on the pipeline. For Magento projects a monorepo with the entire shop code is recommended: custom modules in app/code/, the theme in app/design/, composer.json and composer.lock, package.json and package-lock.json, and the .gitlab-ci.yml. What does not belong in the repository: vendor/, generated/, pub/static/ (produced by the build), var/ (runtime data), app/etc/env.php (contains secrets) and auth.json (Composer credentials).
The composer.lock file absolutely should be checked into the repository, because it pins the exact versions of every dependency and guarantees reproducible builds. Without composer.lock, composer install can install different patch versions of dependencies on every build, which leads to non reproducible artifacts. The same applies to package-lock.json: it belongs in the repository so that npm ci is guaranteed to install exactly the same npm packages every time.
# Recommended Magento repository structure
# app/code/Vendor/Module/ : Custom modules
# app/design/frontend/Vendor/ : Custom theme
# app/etc/config.php : Committed (no secrets)
# composer.json : Committed
# composer.lock : Committed (reproducible builds)
# package.json : Committed
# package-lock.json : Committed
# .gitlab-ci.yml : Committed
# NOT in repository (add to .gitignore):
# vendor/ : Generated by composer install
# generated/ : Generated by setup:di:compile
# pub/static/ : Generated by setup:static-content:deploy
# var/ : Runtime data (logs, cache, sessions)
# app/etc/env.php : Contains secrets (DB password, Redis URL)
# auth.json : Composer auth (use CI variable instead)
# .env : Environment file (use CI variables)
3. Setting up .gitignore for Magento correctly
The .gitignore file for Magento needs to be complete and configured correctly from the start, because adding entries later does not automatically remove Git objects that are already tracked from the index. It is especially critical that app/etc/env.php and auth.json never end up in the repository, since they contain database passwords, Redis URLs and Composer credentials. A file with secrets that was once committed into the Git history has to be removed completely from that history using involved procedures such as git filter-branch or the BFG Repo Cleaner.
Magento ships with a default .gitignore that covers the most important generated directories. It should be extended with project specific additions: IDE configuration folders such as .idea/ and .vscode/, temporary build files, local configuration files and anything that could contain secrets. When creating a new project, GitLab offers the option to generate a .gitignore from a template. For Magento the PHP template should be chosen as a starting point and then extended manually.
4. Default branch and settings
The default branch is the branch new repositories point to by default and the one preselected as the target for merge requests. For Magento projects the default branch should be named main, not master (an outdated convention) and not develop (which confuses the default branch with a development branch). main represents the current production code and is the branch releases are cut from.
Under Settings > General > Visibility, project features, permissions, the following options should be configured for production Magento projects: visibility set to Private, merge requests set to Enabled, issues enabled as needed. The Delete source branch after merge option under Settings > General > Merge request should be enabled so the branch tree stays tidy. The Squash commits when merging option can be set to Encouraged so that merge requests leave a clean commit history.
# GitLab project settings: configure before first pipeline run
# Settings > General > Visibility:
# Visibility level: Private
# Issues: Enabled (optional)
# Merge Requests: Enabled (required)
# Pipelines: Enabled (required)
# Settings > General > Merge request:
# Merge method: Merge commit (or Squash)
# Squash commits: Encouraged
# Delete source branch: Enabled (checked by default)
# Pipelines must succeed: Enabled
# All discussions must be resolved: Enabled
# Settings > Repository > Default branch:
# Default branch: main
# Verify correct default branch is set
# git remote add origin git@gitlab.mironsoft.de:mironsoft/shop.git
# git push -u origin main
# Then set main as default in GitLab UI
5. Branch protection: main and release/*
Protected branches are the most important technical control mechanism for deployment security in GitLab. For main the strictest configuration applies: Allowed to push: No one, Allowed to merge: Maintainers (or Developers, if the team understands the process). This setting ensures that no developer can push directly to main, whether by accident or through a force push. Every change has to go through a merge request that gets reviewed and approved.
The release/* pattern protects every branch that starts with release/. These branches are used for release candidates and hotfix processes: a release branch release/1.2.3 is created from main, receives final adjustments and is then merged back into main. Protected branches for release/* prevent direct pushes to these branches and require the same review process as main. The Allowed to force push: No setting should be enabled on every protected branch: force pushes to protected branches can destroy commit history and are never necessary in a productive workflow.
6. Merge requests and approval rules
Merge requests are the controlled path by which code reaches protected branches. For Magento projects the following approval rules should be configured: at least one approval from a maintainer is required before a merge request can be merged into main. For security critical files such as .gitlab-ci.yml, composer.json and configuration files in app/etc/, GitLab Premium allows setting up a code owner system that forces a specific reviewer for these files.
Integrating the pipeline with merge requests is an important quality mechanism: under Settings > General > Merge request the Pipelines must succeed option can be enabled. That means a merge request can only be merged once every pipeline job has run successfully, including PHPStan, PHPUnit and lint checks. This mechanism prevents faulty code from reaching main, which would cause the next pipeline to fail.
# Branch protection configuration summary
# Settings > Repository > Protected Branches
# main branch
# - Allowed to push: No one
# - Allowed to merge: Maintainers
# - Allowed to force push: No
# - Code owner approval: Yes (GitLab Premium)
# - Pipelines must succeed: Yes (via MR settings)
# release/* branches
# - Allowed to push: No one
# - Allowed to merge: Maintainers
# - Allowed to force push: No
# development branches (feature/*, fix/*)
# - No protection (developers push freely)
# - MR required to merge into main or release/*
# Settings > Repository > Protected Tags
# - v* pattern
# - Allowed to create: Maintainers
7. Protected tags for release approvals
Protected tags matching the v* pattern are the trigger signal for production deployments. The logic is simple and effective: a maintainer creates a tag following the pattern v1.2.3, that tag is protected by the protection rule and can only be created by maintainers, and the pipeline only runs the production deploy job if the commit was triggered by such a tag. This creates a technical barrier that structurally rules out unintended production deployments.
Semantic Versioning is a good fit for versioning: a major version for incompatible API changes, a minor version for new features and a patch version for bug fixes. For Magento this is not just a convention but also practical: tags like v1.2.3 make it immediately obvious in the GitLab pipeline overview what was released. An annotated tag (git tag -a v1.2.3 -m "Release 1.2.3: checkout fix and performance improvements") includes a description and is the recommended form for release tags.
8. Setting up deploy keys and SSH access
Deploy keys let an external system, in this case the GitLab Runner or a deployment script, access the repository read only or read write without using a personal access token that carries full user permissions. Magento deployments typically do not need read write access: the runner checks out the code and transfers it to the server, but does not write back into the repository.
Besides deploy keys for the repository, the pipeline needs SSH access to the deployment servers. This is managed via a CI/CD variable called SSH_PRIVATE_KEY, which holds the private SSH key of the deployment user. The public key needs to be added to ~/.ssh/authorized_keys of the deployment user on every deployment server. A new deployment environment should always get a dedicated deployment user without sudo rights, following the principle of least privilege.
# SSH setup for GitLab CI/CD deployment
# Generate dedicated deployment keypair (outside of repository):
# ssh-keygen -t ed25519 -C "gitlab-deploy@mironsoft.de" -f gitlab-deploy
# Public key (gitlab-deploy.pub) -> add to server:
# cat gitlab-deploy.pub >> /home/deploy/.ssh/authorized_keys
# chmod 600 /home/deploy/.ssh/authorized_keys
# Private key (gitlab-deploy) -> add to GitLab CI/CD variable:
# Settings > CI/CD > Variables
# Name: SSH_PRIVATE_KEY
# Value: <contents of gitlab-deploy>
# Protected: Yes, Masked: Yes, Scope: production
# Known hosts -> get with:
# ssh-keyscan -H web01.mironsoft.de
# Add output to CI/CD variable SSH_KNOWN_HOSTS
# Usage in .gitlab-ci.yml:
# before_script:
# - eval $(ssh-agent -s)
# - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
# - mkdir -p ~/.ssh && chmod 700 ~/.ssh
# - echo "$SSH_KNOWN_HOSTS" >> ~/.ssh/known_hosts
9. Setup variants compared
How extensive the project setup needs to be depends on team size and risk profile. For a solo developer project without a team, some rules can be relaxed, but even then the repository structure, the .gitignore and the protected tags should be fully configured. For team projects with multiple developers and a production environment, fully configuring every governance rule is mandatory.
| Setup element | Solo developer | Small team | Larger team |
|---|---|---|---|
| Repository structure | Complete | Complete | Complete |
| Protected main branch | Optional | Recommended | Mandatory |
| Merge request approval | Not necessary | Optional | Mandatory |
| Protected tags v* | Recommended | Recommended | Mandatory |
| Deploy keys | Mandatory | Mandatory | Mandatory |
The rule of thumb: everything related to security, the .gitignore for secrets, protected tags for release control, deploy keys instead of personal tokens, is always mandatory regardless of team size. Governance rules for review processes can be scaled, but security rules cannot.
10. Summary
The GitLab project setup for Magento covers five essential steps: a repository structure with a correct .gitignore, setting the default branch to main, configuring protected branches for main and release/*, setting up protected tags matching v* for release approvals, and managing SSH access through dedicated deployment keys. These settings form the organizational foundation that every pipeline job builds on.
The most common mistake is not misconfiguring one of these settings, but skipping it because it "can be done later." In reality, later often never comes, because the project is already running and reconfiguring it disrupts active processes. Completing the setup fully before the first pipeline run saves you from doing governance work at an inconvenient moment, namely right when the first critical deployment is due.
GitLab project setup, the essentials at a glance
Repository structure
.gitignore complete from the start, especially env.php and auth.json. Never commit secrets. Always commit composer.lock and package-lock.json.
Branch protection
Protect main and release/*, no direct pushing. Every change goes through merge requests with a mandatory pipeline check.
Protected tags
Pattern v* reserved for maintainers only. A technical barrier for production deployments. Semantic Versioning with annotated tags.
SSH & deploy keys
Dedicated deployment user without sudo. SSH key stored as a protected and masked CI variable. Prepare SSH_KNOWN_HOSTS.