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
syslogcontains general system and service activity, including Cron events.auth.logrecords authentication-related activity.nginx/access.logrecords requests handled by Nginx.
They also compare several ways to read log files:
| Command | Purpose |
|---|---|
tail FILE | Show the last part of a file. |
head FILE | Show the first part of a file. |
less FILE | Read a file one page at a time. |
cat FILE | Print the entire file. |
tail -f FILE | Keep 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.