Skip to main content

Cron for CI

A. What Is Cron?

We already created a Bash script that can pull the latest code from GitHub:

#!/usr/bin/bash

git pull origin main

This automates the action, but we still need a way to run the action automatically on a schedule.

This is where Cron comes in.

Cron is a time-based job scheduler on Unix-like systems.

Conceptually:

Bash Script
→ What should happen?

Cron
→ When should it happen?

For this example:

The course describes Cron as essentially a timer that executes something at a specified cadence.


B. Cron Job Syntax

A Cron job contains scheduling information followed by the command that should be executed.

The schedule contains five fields:

* * * * *
│ │ │ │ │
│ │ │ │ └── Day of week
│ │ │ └──── Month
│ │ └────── Day of month
│ └──────── Hour
└────────── Minute

So the general structure is:

Minute Hour Day Month Day-of-Week Command

For example:

* * * * *

means the job runs every minute.

Cron syntax can look confusing at first, but each field simply represents a different part of the schedule.


C. Using * in Cron

The asterisk:

*

means:

Every possible value for this field.

For example:

* * * * *

means:

Every minute
Every hour
Every day
Every month
Every day of the week

Therefore, the command runs:

Every minute.

The course wants the GitHub script to run every one or two minutes.


D. Running a Job at an Interval

Cron can also run something at regular intervals.

For example:

*/10 * * * *

means:

Run every 10 minutes.

Conceptually:

*/10

Every 10th minute

Similarly:

*/2 * * * *

means:

Run every 2 minutes.

For this course, either every minute or every two minutes is sufficient for demonstrating the automatic GitHub pull.


E. Use Crontab Guru

Cron expressions can be difficult to remember.

The course recommends using crontab.guru to help construct and understand Cron schedules.

Instead of trying to memorize every combination:

This makes it easier to verify that a Cron expression represents the schedule you actually want.


F. Edit Cron Jobs with crontab

Cron jobs for a user can be edited using:

crontab -e

The -e means:

Edit the user's crontab.

The first time it runs, the system may ask which text editor should be used.

The course chooses Vim.

Conceptually:


G. Schedule the GitHub Script

Open the crontab:

crontab -e

The course creates a Cron job that runs the github.sh script on a schedule.

For example:

*/2 * * * * sh /var/www/app/github.sh

Breaking it down:

*/2 * * * *
→ Run every two minutes

sh
→ Use the shell to execute the script

/var/www/app/github.sh
→ The script to execute

Conceptually:

The course later changes the schedule to every minute temporarily so that it can test the Cron job without waiting two minutes.

H. Check Whether Cron Is Running

After creating the Cron job, we need to verify that it actually runs.

The course first looks at:

/var/log/syslog

The system log records activity happening on the machine.

For example:

sudo cat /var/log/syslog

However, cat prints the entire file, which can become inconvenient for large log files.

To watch new log entries as they appear, the course uses:

sudo tail -f /var/log/syslog

tail shows the end of a file.

-f means:

Follow the file as new lines are added.

The slides group common server logs under /var/log/:

/var/log/syslog
/var/log/auth.log
/var/log/nginx/access.log
  • syslog contains general system and service activity, including Cron events.
  • auth.log records authentication-related activity.
  • nginx/access.log records requests handled by Nginx.

They also compare several ways to read log files:

CommandPurpose
tail FILEShow the last part of a file.
head FILEShow the first part of a file.
less FILERead a file one page at a time.
cat FILEPrint the entire file.
tail -f FILEKeep following new lines appended to a file.

Conceptually:


I. Cron Does Not Run from the Application Directory

The first version of the script contains:

git pull origin main

There is a problem.

When we manually run the script from the application directory, Git knows which repository we are working with.

For example:

/var/www/app

git pull origin main

But Cron does not automatically execute the script from that directory.

Therefore, the script cannot assume that its current working directory is:

/var/www/app

The course fixes this by explicitly changing directories before running Git:

#!/usr/bin/bash

cd /var/www/app # add this line
git pull origin main

Conceptually:

The important lesson is:

If a script depends on being executed from a specific directory, explicitly specify that directory.


J. Use Fast-Forward Only

The course also adds the --ff-only option to the Git pull.

Conceptually:

git pull --ff-only origin main

The goal is to avoid automatically creating more complicated Git history when the server's local branch has diverged from the remote branch.

With fast-forward only:

This makes the automated pull safer and more predictable.

The course describes it as a way to pull changes without colliding with existing local changes or requiring an automatic rebase.


K. Cron Logs vs. Script Output

syslog is a system log that records many important events and messages generated by the operating system and system services.

For example, it may contain:

syslog
├── Cron activity
│ └── Cron started a scheduled job

├── Service messages
│ └── A system service started, stopped, or reported a problem

├── System events
│ └── General operating-system events

└── Messages explicitly sent to the system logger
└── logger "Deployment completed"

However, syslog does not automatically record everything that happens on the server.

For example, the normal output or error output produced by an arbitrary command is not automatically written to syslog.

In our example, syslog can show that Cron executed the scheduled job.

We can monitor it with:

sudo tail -f /var/log/syslog

Conceptually:

However, github.sh itself runs:

git pull --ff-only origin main

and Git may produce output such as:

Already up to date.

or:

fatal: ...

These are the command's stdout and stderr, and they are not automatically written to syslog.

Therefore:

At this point, we can confirm that Cron ran the job, but we still need another step to capture what actually happened inside github.sh.


L. Standard Output and Standard Error

Unix commands work with three standard streams:

Command
├── stdin → Standard Input
├── stdout → Standard Output
└── stderr → Standard Error

Standard Input (stdin) is the data a command reads. It may come from the keyboard, a file, or another command.

Standard Output (stdout) is the normal output produced by a command.

For example:

Already up to date.

Standard Error (stderr) contains error messages.

For example:

fatal: ...

These streams provide a consistent interface for Unix commands. Because one command's output can become another command's input, small commands can be chained into more useful workflows.

stdout and stderr are not automatically written to syslog.

For an automated job, we want to capture both so that we can determine whether the command succeeded or failed.

Conceptually:

git pull

├── stdout → Success / normal information

└── stderr → Error information

M. Redirection

Unix provides redirection operators to control where a command receives its input and where its output goes.

Common redirection operators include:

| → Pass stdout to another command
> → Write stdout to a file
>> → Append stdout to a file
< → Read stdin from a file
2>&1 → Redirect stderr to stdout

| — Pipe stdout to Another Command

The pipe operator | sends the standard output (stdout) of one command to the standard input (stdin) of another command.

Example:

cat /var/log/syslog | grep CRON

Flow:

Instead of displaying all of syslog, grep receives the output and filters it.


> — Write stdout to a File

The > operator redirects standard output to a file.

Example:

echo "Hello" > output.txt

Result:

output.txt

Hello

If the file already contains:

Old content

running the command replaces it with:

Hello

So:

> writes to the file and overwrites existing content.


>> — Append stdout to a File

The >> operator also redirects standard output to a file, but it adds the new output to the end of the file.

Suppose:

output.txt

Hello

Then run:

echo "World" >> output.txt

The result becomes:

Hello
World

So:

> → Write / overwrite
>> → Append

< — Read stdin from a File

The < operator uses a file as the standard input (stdin) of a command.

Suppose:

names.txt

Wilson
John
Amy

Run:

sort < names.txt

The sort command receives the contents of names.txt as its input.

Output:

Amy
John
Wilson

Conceptually:


2>&1 — Redirect stderr to stdout

Unix represents the standard streams using file descriptors:

0 → stdin
1 → stdout
2 → stderr

Normally:

stdout → Terminal
stderr → Terminal

We can redirect only stdout to a file:

git pull > git.log

This is equivalent to:

git pull 1> git.log

Result:

stdout (1) → git.log
stderr (2) → Terminal

If we also want stderr to go to the same destination as stdout, we can use:

git pull > git.log 2>&1

Breaking it down:

> git.log
→ Redirect stdout to git.log

2>&1
→ Redirect stderr (2) to the current destination of stdout (1)

Therefore:

git pull
├── stdout (1) ───────→ git.log
└── stderr (2) ─2>&1──→ git.log

The & tells the shell that 1 refers to file descriptor 1, not a file named 1.

For comparison:

2> error.log

means:

stderr → error.log

while:

2>&1

means:

stderr → the current destination of stdout

Redirections are processed from left to right, so their order matters.

git pull > git.log 2>&1

means:

1. stdout → git.log
2. stderr → stdout's current destination
3. Therefore, both → git.log

If we want to preserve the existing contents of git.log, use >> instead:

git pull >> git.log 2>&1
> → Write to the file and overwrite existing content
>> → Append to the file

2>&1
→ Make stderr follow stdout's current destination

So > / >> and 2>&1 have different responsibilities:

> or >>
→ Controls how stdout is written to a file

2>&1
→ Controls where stderr is redirected

N. Send Cron Output to syslog

To capture both the normal output and errors from github.sh, update the Cron job to redirect stderr into stdout, then pipe the combined stream to logger:

*/2 * * * * sh /var/www/app/github.sh 2>&1 | logger -t github.sh

Breaking it down:

2>&1
→ Send stderr to the same destination as stdout

|
→ Pipe the combined output into the next command

logger -t github.sh
→ Write the input to the system log with the tag github.sh

The -t tag is optional, but it makes entries easier to identify in syslog.


O. Verify the Automatic Git Pull

After fixing the working directory and logging, the course checks:

sudo tail -f /var/log/syslog

Now the output from the scheduled Git pull appears correctly.

If there are no new commits, Git reports that the repository is already up to date.

The important part is that the process is now happening automatically:

The developer no longer needs to SSH into the server and manually run:

git pull origin main

every time code changes.


P. The Complete Simplified CI/CD Flow

The final setup can be understood as:

The main components are:

GitHub
→ Stores the latest code

Bash Script
→ Defines what should happen

Cron
→ Defines when it should happen

Git
→ Pulls the latest code

Logger / syslog
→ Lets us observe what happened

This creates the course's simplified "fake CI/CD pipeline."

It demonstrates the automation concept, but it is not a complete production CI/CD pipeline because it does not include the testing and validation stages discussed earlier.