In today’s blog post, we will learn .gitlab-ci.yml
file script keyword. In particular, what is before_script, script, and after_script keywords in .gitlab-ci.yml, how to define before_script, script, and after_script with example, comparison between before_script, script, and after_script blocks.
The script block is used to define what action you want to perform in your GitLab CI/CD pipeline. In the script block you write the shell commands that GitLab Runner will execute to perform tasks like compiling code, running tests, deploying applications, or anything else you need to run.
The before_script block is used to execute some commands before the script block start execution (hence the name before_script). For example, you can setup the directory structure, download and install any missing packages that your script block will need.
The after_script block is used to execute some commands after the script block has finished execution (hence the name after_script). For example, you can run clean ups, commits, generate reports, upload artifacts etc. in the after_script block.
Below is a sample .gitla-ci.yml
file with all the three blocks for your reference.
stages:
- install
example:
stage: install
before_script:
- echo "I got executed inside before_script"
script:
- echo "I got executed inside script"
after_script:
- echo "I got executed inside after_script"

The script Keyword: The Main Act
Every job in your .gitlab-ci.yml
file must have a script
section. This is a YAML sequence (list) of commands that are executed in the shell environment provided by your job’s Docker image (or runner’s environment).
Key characteristics of script
:
- Sequential Execution: Commands within the
script
block are executed sequentially, one after another. - Fail-Fast: If any command in the
script
returns a non-zero exit code (indicating failure), the job immediately fails, and the pipeline typically stops (unlessallow_failure: true
is set for the job). - Context: Commands run in the project’s root directory by default.
Example:
stages:
- build
- test
build_job:
stage: build
script:
- echo "Starting the build process..."
- npm install # Install Node.js dependencies
- npm run build # Execute the build command
- echo "Build finished successfully!"
test_job:
stage: test
script:
- echo "Running unit tests..."
- npm test # Execute tests
- echo "Tests passed!"
In build_job
, npm install
runs first, then npm run build
. If npm install
fails, npm run build
will not execute, and the build_job
will fail.
before_script: Setting the Stage
The before_script
keyword allows you to define commands that run before the script
of a job. This is incredibly useful for common setup tasks that apply to multiple jobs or a specific job.
Key characteristics of before_script
:
- Execution Order: Runs after fetching the repository and before the
script
block. - Scope: Can be defined globally (at the top level of
.gitlab-ci.yml
, applying to all jobs) or per-job. A job’sbefore_script
overrides or extends the globalbefore_script
. - Fail-Fast: If a command in
before_script
fails, the job fails, and thescript
block will not execute.
When to use before_script
:
- Installing common dependencies: E.g.,
apt-get update && apt-get install -y <package>
,bundle install
,npm ci
. - Logging in to external services: E.g.,
docker login
. - Environment setup: Setting environment variables or configuring tools.
Example:
stages:
- build
- test
default:
image: node:18 # Base image for all jobs
before_script:
- echo "Running global before_script..."
- npm install --prefer-offline # Install dependencies for all jobs
build_job:
stage: build
script:
- echo "Building application..."
- npm run build
test_job:
stage: test
before_script: # This overrides the default before_script for test_job
- echo "Running test_job specific before_script..."
- npm ci # Use clean install for tests
script:
- echo "Running tests..."
- npm test
In test_job
, the npm ci
command from its specific before_script
will run instead of npm install --prefer-offline
from the default
before_script
.
after_script: Cleaning Up or Reporting
The after_script
keyword defines commands that run after the script
of a job, regardless of whether the script
succeeded or failed.
Key characteristics of after_script
:
- Execution Order: Runs after the
script
block has completed (either successfully or with a failure). - Scope: Can be defined globally or per-job. A job’s
after_script
overrides or extends the globalafter_script
. - Independence from
script
success: Commands inafter_script
will always run, making it ideal for cleanup. - Failure: If a command in
after_script
fails, the job’s overall status will still be determined by thescript
‘s outcome. Theafter_script
failure will be noted, but it would not retroactively fail a job that passed itsscript
.
When to use after_script
:
- Cleanup: Removing temporary files, stopping services.
- Reporting: Sending notifications, uploading logs, posting status updates to external systems.
- Artifact archiving: If not handled by
artifacts
keyword directly.
Example:
stages:
- build
build_and_cleanup:
stage: build
script:
- echo "Building something..."
- mkdir temp_output
- echo "Build content" > temp_output/result.txt
after_script:
- echo "Cleaning up temporary files..."
- rm -rf temp_output
- echo "Reporting job status..."
- curl -X POST -d "status=completed" https://example.com/api/report
Even if the script
fails in build_and_cleanup
, the rm -rf temp_output
and curl
commands will still execute.
Choosing the Right Script Block: before_script vs. script vs. after_script
Feature | script | before_script | after_script |
---|---|---|---|
Purpose | Core job execution | Pre-execution setup | Post-execution cleanup/reporting |
Defined In | .gitlab-ci.yml (Job-level) | .gitlab-ci.yml (Global or Job-level) | .gitlab-ci.yml (Global or Job-level) |
Execution Order | After before_script | Before script | After script |
Failure Behavior | Job fails if command fails | Job fails if command fails | Job status based on script ; after_script failure noted but does not change script outcome |
Access to Project Code | Yes | Yes | Yes |
Use Cases | Build, test, deploy | Install dependencies, login | Cleanup, notifications, logs |
FAQs – before_script, script, after_script
What is the before_script
keyword in GitLab CI/CD?
The before_script
keyword defines a list of shell commands that run before the main script
of a job. It is often used to perform setup tasks such as installing dependencies, configuring environment variables, or preparing the build environment.
How do I use before_script
in a GitLab job?
You can use before_script
inside a job to run pre-commands before the job’s main script
:
test-job:
before_script:
- echo "Setting up test environment"
- npm install
script:
- npm test
In this example, the echo
and npm install
commands run before npm test
.
Can I define a global before_script
for all jobs?
Yes. You can define a global before_script
at the top level of your .gitlab-ci.yml
file. It applies to all jobs, unless a job overrides it:
before_script:
- echo "Global setup step"
- apt-get update
job1:
script:
- echo "Running job1"
job2:
script:
- echo "Running job2"
Both job1
and job2
will run the global before_script
commands first.
How do I override the global before_script
for a specific job?
To override the global before_script
, you simply define a new one in your job. This replaces the global version:
before_script:
- echo "Global setup"
custom-job:
before_script:
- echo "Custom setup for this job only"
script:
- echo "Running custom job"
In this case, custom-job
only runs its own before_script
.
Can I skip the global before_script
for a job?
Yes. To skip the global before_script
, define an empty list in your job:
no-setup-job:
before_script: []
script:
- echo "Running without global before_script"
This completely disables the global before_script
for no-setup-job
.
What is the difference between before_script
and script
in GitLab CI/CD?
before_script
: Runs before the main job logic. Commonly used for setup steps.script
: Contains the main commands that the job is supposed to execute.
The commands in before_script
and script
are both executed in the same shell session, so environment variables or changes made in before_script
persist into script
.
Can I use variables inside before_script
?
Yes. You can use environment variables, including custom or predefined ones:
variables:
SETUP_DIR: "/opt/app"
prepare:
before_script:
- echo "Setting up in $SETUP_DIR"
- cd $SETUP_DIR
script:
- ls
This makes your configuration dynamic and reusable.
Can I define before_script
using anchors for reuse?
Yes. You can define a before_script
block as a YAML anchor and reuse it in multiple jobs:
.default-before: &setup_steps
- echo "Shared setup"
- setup.sh
job1:
before_script: *setup_steps
script:
- echo "Job 1"
job2:
before_script: *setup_steps
script:
- echo "Job 2"
This avoids repeating the same setup logic in every job.
Are before_script
commands executed even if the job fails early?
Yes, before_script
commands are always executed first, and if any of them fail, the job stops immediately and is marked as failed. They must all succeed for the main script
to run.
Can I use before_script
in combination with after_script
?
Absolutely. Use before_script
for pre-job setup and after_script
for post-job cleanup:
build:
before_script:
- echo "Setup"
script:
- make build
after_script:
- echo "Cleanup"
This structure provides a clean separation between setup, execution, and teardown.
What is the script
keyword in GitLab CI/CD?
The script
keyword defines the core commands that a job will execute in GitLab CI/CD. It is a required keyword in every job and contains the actual tasks that GitLab Runner performs, such as compiling code, running tests, or deploying applications.
How do I use the script
keyword in a job?
You use the script
keyword by specifying a list of shell commands under any job:
build:
script:
- echo "Building the app..."
- make build
The commands listed under script
will be executed in sequence inside the GitLab Runner shell.
Is script
mandatory for every job in GitLab CI/CD?
Yes. The script
keyword is required in every job unless the job uses a predefined GitLab template that includes a script internally. Without script
, the job will fail to run or will be ignored during pipeline execution.
Can I define multiple commands under the script
keyword?
Yes. The script
keyword accepts a list of shell commands that are run one after another:
test:
script:
- echo "Starting tests"
- npm install
- npm test
Each command runs in the same shell session, so variables and directory changes persist between commands.
What shell environment does the script
run in?
By default, GitLab CI/CD jobs execute the script
commands in a Bash shell, unless you explicitly define another shell in the GitLab Runner configuration. The shell is typically a Unix-like shell (or PowerShell on Windows runners).
Can I use inline shell operators like &&
or ||
in script
?
Yes. You can combine commands on the same line using operators:
build:
script:
- make clean && make all
However, it is generally better to list each command on a separate line to improve readability and error tracking.
Can I use environment variables inside script
?
Yes, both custom-defined and GitLab predefined environment variables can be used in script
commands:
variables:
BUILD_DIR: build
job:
script:
- mkdir -p $BUILD_DIR
- cd $BUILD_DIR
- echo "Build started in $BUILD_DIR"
GitLab provides many built-in variables like $CI_COMMIT_BRANCH
, $CI_JOB_NAME
, etc.
What happens if a command in the script
fails?
If any command fails (returns a non-zero exit code), the job is marked as failed, and no subsequent commands in the script are executed unless you handle errors manually:
job:
script:
- some_command || echo "Handled failure" # Command failure would not stop the job
You can also use set -e
to explicitly stop execution on the first error (though it is on by default in most runners).
Can I use a shell script file instead of inline script
commands?
Yes. You can execute a standalone shell script by calling it from the script
section:
job:
script:
- ./scripts/deploy.sh
This is useful for complex logic that would clutter the .gitlab-ci.yml
file.
Can I override the script
keyword in extended jobs?
Yes. When using the extends
keyword or YAML anchors, you can override or modify the script
section:
.default-job: &defaults
script:
- echo "Default"
custom-job:
<<: *defaults
script:
- echo "Custom script overrides default"
The custom-job
will use its own script
and not the one in defaults
.
Is the script
executed inside a Docker container if one is specified?
Yes. If your job specifies a image:
(Docker container), then the script
runs inside that container:
job:
image: node:20
script:
- node --version
This allows you to run commands in specific runtime environments without installing dependencies globally on the runner.
Where can I see the output of the script
execution in GitLab?
The output of the script
section is visible in the Job Logs under the CI/CD → Pipelines section of your GitLab project. Each command’s output and errors are logged in real-time.
What is the after_script
keyword in GitLab CI/CD?
The after_script
keyword in GitLab CI/CD specifies commands that run after the main job script, regardless of whether the job succeeds or fails. It is commonly used for cleanup tasks, such as deleting temporary files, stopping services, or uploading logs.
How do I use the after_script
keyword in a job?
You can define after_script
inside a job to run cleanup or post-processing commands:
cleanup-job:
script:
- ./deploy.sh
after_script:
- echo "Cleaning up..."
- rm -rf temp/
In this example, rm -rf temp/
will execute after the ./deploy.sh
command, even if the deployment fails.
Can I define a global after_script
for all jobs?
Yes. You can define after_script
at the top level of the .gitlab-ci.yml
file. It applies to every job, unless explicitly overridden by a job-level after_script
.
after_script:
- echo "Global cleanup"
job1:
script:
- echo "Doing work in job1"
job2:
script:
- echo "Doing work in job2"
Both job1
and job2
will run the global after_script
at the end.
How do I override a global after_script
for a specific job?
You can override a global after_script
by specifying a new one inside a job:
after_script:
- echo "Global after_script"
custom-job:
after_script:
- echo "Custom after_script for this job"
script:
- echo "Main job logic"
Here, custom-job
will run only its own after_script
and not the global one.
Can I completely disable the global after_script
in a job?
Yes. You can skip the global after_script
for a specific job by setting an empty array:
no-cleanup-job:
after_script: []
script:
- echo "Run without global after_script"
This job will not execute the global after_script
.
When does after_script
run during job execution?
The order of job execution is as follows:
before_script
(if defined)script
(main job logic)after_script
(always runs, even ifscript
fails)
This ensures that cleanup or reporting tasks are always executed.
Can I use environment variables in after_script
?
Yes. You can use both predefined and custom environment variables:
variables:
TEMP_DIR: temp_data
cleanup:
script:
- echo "Running job..."
after_script:
- echo "Removing $TEMP_DIR"
- rm -rf $TEMP_DIR
This makes your post-job logic flexible and dynamic.
What happens if an after_script
command fails?
The job is marked as passed or failed based on the outcome of the script
, not the after_script
. If an after_script
command fails, the error is shown in the job log, but the job status will not change.
Can I use after_script
to upload logs or reports?
Yes. A common use case is uploading test logs, coverage files, or build artifacts:
test:
script:
- npm test > output.log
after_script:
- curl --upload-file output.log https://logs.example.com/upload
You can also use it to notify external systems or clean up cloud infrastructure.
Is after_script
suitable for deployment rollback or alerts?
Yes. You can use after_script
to trigger rollback scripts or send notifications if the deployment job fails:
deploy:
script:
- ./deploy.sh || exit 1
after_script:
- if [ $? -ne 0 ]; then ./rollback.sh; fi
However, for complex rollback logic, it is better to separate it into a dedicated job using rules
or when: on_failure
.
Can I reuse after_script
using YAML anchors in multiple jobs?
Yes. You can define a shared block using YAML anchors and reuse it:
.shared-cleanup: &clean_steps
- echo "Shared cleanup"
- rm -rf logs/
job1:
script: echo "Job 1"
after_script: *clean_steps
job2:
script: echo "Job 2"
after_script: *clean_steps
This makes your configuration cleaner and avoids duplication.
Author

Experienced Cloud & DevOps Engineer with hands-on experience in AWS, GCP, Terraform, Ansible, ELK, Docker, Git, GitLab, Python, PowerShell, Shell, and theoretical knowledge on Azure, Kubernetes & Jenkins. In my free time, I write blogs on ckdbtech.com