GitLab Scheduled Pipelines: Automating Nightly Builds, Security Scans and Maintenance Jobs
AI generated
CI/CD
.yml
GitLab · CI/CD · Automation
GitLab Scheduled Pipelines: Automating Nightly Builds and Maintenance
cron syntax, variables and the difference to a server cronjob

Not every pipeline needs to be triggered by a push or a merge request. Nightly security scans, recurring cleanup jobs or regular dependency updates run best on a time based schedule, regardless of whether anyone actually committed code that day. GitLab's Scheduled Pipelines provide a cron mechanism built directly into the platform for exactly this. This article covers the setup, the cron syntax, working with pipeline variables and why a Scheduled Pipeline is fundamentally different from a classic cronjob on a server.

15 min read Scheduled Pipelines cron Syntax Maintenance Jobs Automation

1. What Time Based Pipelines Are Good For

The classic pipeline triggers in GitLab, namely push, merge request and manual trigger, cover the day to day of software development well, but they leave out an important category of tasks: everything that needs to happen regularly and independently of code changes. A nightly security scan should still run even if nobody committed anything that day. A weekly dependency update should check whether new versions are available, without anyone having to start the pipeline by hand.

This is exactly what Scheduled Pipelines are for. They are configured under CI/CD > Schedules in the project, and GitLab triggers the stored .gitlab-ci.yml as if a pipeline trigger had arrived from the outside, except CI_PIPELINE_SOURCE is set to schedule instead of push. That makes it possible to distinguish between regular commit pipelines and time based runs within the very same pipeline file, and to give them a different set of jobs, without maintaining two separate pipelines.

2. Setup: Creating a Schedule in the GitLab UI

A new schedule is created under Build > Pipeline schedules and consists at its core of four pieces of information: a description, the cron expression for the timing, the time zone, and the target branch or tag the pipeline runs against. In addition, each schedule can carry its own CI/CD variables that are only set for this particular schedule and never appear in any other pipeline. It is important that the executing user, under whose identity the pipeline runs, has sufficient permissions for every job that gets referenced.

Once created, a schedule can be triggered manually at any time via the Run now button, which is indispensable for testing the configuration. Only once a manual test run completes successfully should you trust that the cron expression will fire as expected. GitLab runs Scheduled Pipelines with a small, technically inherent delay of up to a few minutes, which should be factored in for time critical use cases.


# .gitlab-ci.yml
nightly-security-scan:
  stage: test
  script:
    - echo "Running nightly security scan"
  rules:
    - if: '$CI_PIPELINE_SOURCE == "schedule"'

3. The cron Syntax in Detail and the Role of the Time Zone

GitLab uses the classic five field cron syntax for schedules: minute, hour, day of month, month and day of week, separated by spaces. The expression 0 2 * * * therefore means every day at two in the morning, while 0 3 * * 1-5 only fires on weekdays at three. Asterisks stand for any possible value in that field, commas separate several specific values, and hyphens define ranges such as 1-5 for Monday through Friday.

A frequently overlooked point is the time zone: it is chosen separately when the schedule is created and is independent of the time zone of the GitLab server or the runners. Anyone who forgets to explicitly set it to their own target time zone may end up puzzled why the nightly run happens in the middle of the day or shifted by several hours, especially on GitLab.com, which defaults to UTC. When clocks change between summer and winter time, the actual execution time can also shift by an hour, which is worth keeping in mind for particularly time sensitive jobs.

4. Using Pipeline Schedule Variables for Different Runs

Every schedule can carry its own CI/CD variables, which are only set during that specific time based run. That makes it possible to reuse the same job in .gitlab-ci.yml for different purposes, for example a generic scan job that either performs a quick daily scan or a comprehensive weekly scan depending on the schedule, controlled through a variable such as SCAN_DEPTH. No separate job definitions need to be maintained for each time based variant this way.

In practice it pays off to create several schedules with clearly worded descriptions, for instance Nightly Quick Scan and Weekly Deep Scan, rather than overloading a single schedule with complex internal branching logic. That makes the configuration in the GitLab UI self explanatory and makes it easy for new team members to understand at a glance which schedule is responsible for what, without having to read the pipeline definition in detail.


# .gitlab-ci.yml
dependency-scan:
  stage: test
  variables:
    SCAN_DEPTH: "quick"
  script:
    - echo "Running dependency scan with depth $SCAN_DEPTH"
  rules:
    - if: '$CI_PIPELINE_SOURCE == "schedule"'

5. Separating Scheduled Jobs From Regular Pipelines With rules

The key to distinguishing a regular commit pipeline from a time based run is the predefined variable CI_PIPELINE_SOURCE. It takes the value schedule for Scheduled Pipelines, while it holds push for a regular push and merge_request_event for a merge request. A rules condition such as if: $CI_PIPELINE_SOURCE == schedule lets you enable a job specifically only for time based runs, or conversely, disable it specifically for regular commit pipelines.

Combining several rules conditions is especially useful for finely distinguishing between different trigger types: an extensive integration test that takes several minutes then runs only on Scheduled Pipelines, while every regular push only triggers a fast unit test run. This keeps the feedback time short for developers, without giving up the more thorough but more time consuming check, which then simply happens automatically at night instead.

6. Why a Scheduled Pipeline Is Not a Classic Server Cronjob

At first glance a Scheduled Pipeline looks like a simple replacement for a crontab entry on a server, but the differences are substantial. A classic cronjob runs directly on the target server, with its local environment, its installed packages and its file system, with no containerization or isolation at all. A Scheduled Pipeline, on the other hand, starts a full GitLab runner job, typically inside a fresh Docker container, including a checkout of the repository, cache restoration, and everything else a regular pipeline brings along.

That has concrete practical consequences: a Scheduled Pipeline automatically gets access to pipeline logs, artifacts, the full job history inside the GitLab UI, and can alert on failure through the same notification mechanisms as any other pipeline, for example email or a Slack integration. A classic server cronjob, by contrast, logs at best to a log file or an email to root, without the structured traceability that GitLab's pipeline history provides out of the box. In exchange, a real cronjob remains the better fit for tasks that must run directly, without container overhead, on the target system itself, such as database maintenance on that same host.

7. Use Case: Nightly Security Scans

A particularly common use case for Scheduled Pipelines is full security scanning that would take too long during the day to run on every push. While a fast SAST scan makes sense on every merge request, a comprehensive dependency or container scan with a full database update can take several minutes. A nightly schedule at two or three in the morning ensures this thoroughness still happens daily, without slowing down the developer feedback loop during the day.

It also helps to automatically send the result of such a nightly scan to a Slack channel or by email to the security team whenever new critical findings appear, instead of relying on someone checking the pipeline overview manually every day. A simple job at the end of the pipeline that triggers a webhook notification on failure closes this gap reliably and ensures critical findings do not go unnoticed for days.


# .gitlab-ci.yml
notify-on-finding:
  stage: notify
  script:
    - |
      if [ "$SCAN_RESULT" = "critical" ]; then
        curl -X POST -H 'Content-type: application/json' \
          --data '{"text":"Critical security finding in nightly scan"}' \
          "$SLACK_WEBHOOK_URL"
      fi
  rules:
    - if: '$CI_PIPELINE_SOURCE == "schedule"'
  needs: ["dependency-scan"]

8. Use Case: Recurring Maintenance Jobs

Besides security scans, Scheduled Pipelines are a great fit for maintenance tasks that need to happen regularly but not on every commit. Examples include cleaning up old, no longer needed Docker images in the project's own container registry, deleting expired feature branch environments, renewing SSL certificates through Let's Encrypt, or creating and verifying regular database backups followed by a restore test in an isolated environment.

The advantage over an external cron tool is that these maintenance jobs live directly in the same repository and under the same versioning as the rest of the application code. Changes to the backup strategy or the cleanup logic go through the same merge request review process as any other code change, which considerably improves traceability and quality assurance for security relevant maintenance tasks compared to a script maintained in isolation somewhere on a server.

9. Best Practices and a Comparison to Alternatives

A solid approach to Scheduled Pipelines starts with giving every schedule a meaningful description and regularly checking whether it is still needed. Orphaned schedules whose original purpose is long obsolete waste CI minutes unnecessarily and cause confusion. It is just as important not to silently ignore failures of Scheduled Pipelines, but to set up a notification, since otherwise a nightly security scan can fail unnoticed for weeks.

Compared to alternatives such as a classic server cronjob or a Kubernetes CronJob object, GitLab offers the advantage of a unified interface, versioning the job definition in the same repository, and seamless integration with existing pipeline variables and secrets. The table below compares the three approaches by their key properties to make the decision easier for a given use case.

Approach Execution Location Traceability Typical Use
GitLab Scheduled Pipeline GitLab runner, containerized Full pipeline history and logs Nightly scans, repository close maintenance
Server cronjob (crontab) Directly on the target server Only a log file or email to root Server local tasks without container overhead
Kubernetes CronJob Inside the Kubernetes cluster kubectl logs, cluster events Recurring jobs in cluster native setups
External scheduler service Outside GitLab and the server Depends on the specific tool Cross system orchestration across platforms

Mironsoft

CI/CD pipelines, zero-downtime deployments and release automation

Deployments that run without downtime and without the nail-biting?

We review existing GitLab pipelines for fragile deployment steps and missing safeguards, then build a release process with zero-downtime deployments, automated checks and a rollback you can actually trust in an emergency.

Pipeline Review

Checking an existing .gitlab-ci.yml for fragility, missing stages and security gaps.

Zero-Downtime Deployment

Building symlink releases, health checks and rollback strategies for Magento stores.

CI/CD Automation

Connecting tests, security scans and deployments into one reliable pipeline.

10. Summary

GitLab Scheduled Pipelines: Key Takeaways

Time based, not push

Scheduled Pipelines run on a cron schedule, regardless of whether anyone committed that day.

CI_PIPELINE_SOURCE

The schedule value reliably distinguishes time based runs from regular commit pipelines.

Not a server cronjob

Scheduled Pipelines run containerized inside the runner, with full log and artifact history in GitLab.

Typical use cases

Nightly security scans, registry cleanup, backup verification and certificate renewal.

11. FAQ: GitLab Scheduled Pipelines: Key Takeaways

1Where do I create a Scheduled Pipeline in GitLab?
Under Build > Pipeline schedules in the project. There you configure the description, cron expression, time zone, target branch and optional variables that only apply to this schedule.
2How does a pipeline distinguish between push and schedule?
Through the predefined variable CI_PIPELINE_SOURCE. It holds the value schedule for a schedule, push for a regular commit, and merge_request_event for a merge request, so rules can respond to it specifically.
3Which cron syntax does GitLab use?
The classic five field syntax with minute, hour, day of month, month and day of week, separated by spaces. The expression 0 2 * * * for example means every day at two in the morning.
4Why doesn't my schedule run at the expected time?
This is usually caused by the time zone chosen when the schedule was created, which is independent of the server's time zone. GitLab.com defaults to UTC, which can look like a shift if your own target time zone was not set explicitly.
5Is a Scheduled Pipeline a replacement for a server cronjob?
Not in general. For tasks that must run directly on a specific server without container overhead, a classic cronjob remains more suitable. For repository close, versioned tasks with full pipeline traceability, the Scheduled Pipeline is the better choice.
6Can I create several schedules for the same project?
Yes, a project can have any number of schedules with different cron expressions, target branches and variables, for example a daily quick scan and a weekly deep scan running in parallel.
7How do I test a newly created schedule without waiting for the next scheduled time?
The Run now button in the schedule overview lets you trigger the schedule manually at any time, with the same variables and settings that would also be used for the automatic cron run.
8How do I get notified when a Scheduled Pipeline fails?
GitLab offers project wide email notifications for failed pipelines. In addition, you can set up a dedicated job that specifically sends a webhook to Slack or another tool on failure.
9Do Scheduled Pipelines consume CI minutes like regular pipelines?
Yes, a time based run consumes the same CI/CD minutes as any other pipeline. Orphaned or too frequently running schedules should therefore be reviewed regularly and disabled if no longer needed.
10Can I reuse Scheduled Pipeline variables in regular pipelines?
No, variables set directly on a schedule only apply to that specific time based run. For values that should apply project wide, regular CI/CD variables in the project settings should be used instead.