Environments, Deployments, Environment Tiers, URLs, Deployment History, and Operational Traceability: Guided Hands-On Workflow and Core Operations
Build once, deploy a verified artifact to a disposable simulated target, create a GitLab environment/deployment record, update it once, and independently verify target state.
Learning objectives
- Create a disposable Free-tier-compatible pipeline that builds one synthetic artifact and deploys it without rebuilding.
- Declare a stable environment name, explicit testing deployment tier, and non-routable training URL.
- Capture source/ref/SHA, pipeline/job IDs, artifact digest, environment metadata, and simulated target receipt before and after an update.
- Use an environment verification job without creating a second deployment record simply to inspect health.
- Clean up only the disposable environment/project evidence and leave unrelated environments/deployments untouched.
1. Disposable scenario: one verified artifact, one stable training environment
The mandatory path needs only a disposable GitLab project with a runner and basic POSIX shell tools. It does not create cloud resources, publish packages, or use credentials. The target is a faithful filesystem snapshot simulation stored as deployment evidence. The point is to learn GitLab environment/deployment semantics and provenance before introducing a real provider.
Use a branch such as ch19/environments. The stable
GitLab environment is training/ch19, tier
testing, URL
https://training.invalid/ch19.
2. Preflight: prove project/source/runner state before mutating anything
Record the project path, branch, current SHA, expected runner/executor, and the fact that no real target exists. Use only a throwaway project or branch you are authorized to modify.
git status --short
git rev-parse HEAD
git branch --show-current
printf 'expected environment=%s
' 'training/ch19'
If the project already has an environment named
training/ch19, inspect its history first. Do not
overwrite someone else’s lab evidence. Use a project-specific
disposable suffix instead while keeping the name stable across this
lab.
3. Create deterministic synthetic source and the build-once contract
mkdir -p src ci
cat > src/app.txt <<'EOF'
GitLab CI/CD Chapter 19 training payload
health=ok
EOF
cat > ci/build.sh <<'EOF'
#!/bin/sh
set -eu
mkdir -p dist
cp src/app.txt dist/app.txt
printf 'source_sha=%s
' "${CI_COMMIT_SHA:-local}" > dist/build-metadata.txt
sha256sum dist/app.txt > dist/app.sha256
EOF
chmod +x ci/build.sh
The deployment job will consume dist/app.txt and
dist/app.sha256; it will not rerun the build. This
makes the provenance rule observable.
4. Pipeline: build → deploy → verify without manufacturing extra deployment history
stages: [build, deploy, verify]
build:release:
stage: build
image: alpine:3.22
script:
- apk add --no-cache coreutils
- ./ci/build.sh
- cat dist/build-metadata.txt
- cat dist/app.sha256
artifacts:
expire_in: 1 week
paths:
- dist/app.txt
- dist/app.sha256
- dist/build-metadata.txt
deploy:training:
stage: deploy
image: alpine:3.22
needs:
- job: build:release
artifacts: true
resource_group: ch19-training-environment
script:
- apk add --no-cache coreutils
- sha256sum -c dist/app.sha256
- mkdir -p simulated-target/current evidence
- cp dist/app.txt simulated-target/current/app.txt
- cp dist/app.sha256 simulated-target/current/app.sha256
- printf 'source=%s
sha=%s
pipeline=%s
job=%s
environment=%s
' "$CI_PIPELINE_SOURCE" "$CI_COMMIT_SHA" "$CI_PIPELINE_ID" "$CI_JOB_ID" "$CI_ENVIRONMENT_NAME" > evidence/deployment-receipt.txt
- sha256sum simulated-target/current/app.txt | tee evidence/target-observed.sha256
environment:
name: training/ch19
deployment_tier: testing
url: https://training.invalid/ch19
artifacts:
expire_in: 1 week
paths:
- simulated-target/current/
- evidence/
verify:training:
stage: verify
image: alpine:3.22
needs:
- job: deploy:training
artifacts: true
script:
- apk add --no-cache coreutils
- sha256sum -c simulated-target/current/app.sha256
- diff -u simulated-target/current/app.sha256 evidence/target-observed.sha256 || true
- grep '^health=ok$' simulated-target/current/app.txt
- printf 'verified_sha=%s pipeline=%s job=%s
' "$CI_COMMIT_SHA" "$CI_PIPELINE_ID" "$CI_JOB_ID"
environment:
name: training/ch19
action: verify
State changes: build:release creates
artifact evidence; deploy:training consumes the exact
artifact and creates a GitLab deployment for
training/ch19; verify:training consumes
target evidence and verifies it but does not pretend another
deployment happened.
5. First deployment: inspect before and after
- Commit and push the lab configuration.
-
Record
CI_PIPELINE_SOURCE, ref,CI_COMMIT_SHA, pipeline ID, build/deploy/verify job IDs. -
Download or inspect
dist/app.sha256; record the artifact digest. -
Open Operate → Environments →
training/ch19. Record the environment ID/state/tier/URL and the deployment ID/status/actor/SHA. -
Inspect
evidence/deployment-receipt.txtandevidence/target-observed.sha256. - Confirm the verify job is successful and did not create a second ordinary deployment event.
Expected observation: one environment identity, one deployment for the first run, one target snapshot whose observed digest equals the build artifact digest.
6. Update once without changing the environment identity
Make a controlled payload change:
printf '
version=2
' >> src/app.txt
git add src/app.txt
git commit -m 'ch19: update synthetic payload'
git push
Record the second pipeline ID/SHA and new artifact digest. The
environment name remains training/ch19, so GitLab
appends another deployment to the same history rather than creating
a separate environment.
Expected evidence: deployment 2 has the second source SHA, the target snapshot shows the second digest, and deployment 1 remains visible as rollback/audit context.
7. Read deployment history as a provenance chain
| Evidence | Run 1 | Run 2 | Why it matters |
|---|---|---|---|
| Source SHA | SHA-A | SHA-B | Names repository revision. |
| Artifact digest | digest-A | digest-B | Names actual bytes. |
| Pipeline/job IDs | P-A/J-A | P-B/J-B | Names execution evidence. |
| Environment | training/ch19 | training/ch19 | Stable target identity. |
| Deployment ID | D-A | D-B | Names GitLab deployment events. |
| Target observed digest | digest-A | digest-B | Proves simulated external state after each event. |
8. Prove the URL is metadata, not health
The lab URL intentionally uses .invalid. The deployment
can be successful even though the URL is not reachable. That is
educational: deployment history and target health cannot be
collapsed.
In a real system, replace the filesystem read-back with a provider/API or HTTP health/version endpoint and compare a version/digest returned by the target with the expected artifact identity. Treat HTTP 200 without version identity as weak evidence.
9. Optional non-secret environment-scope demonstration
Create a harmless project variable such as
TRAINING_LABEL=chapter19 scoped only to
training/ch19. Print only the non-secret value from the
deployment or verify job to prove environment-scoped availability.
Do not use environment-scoped variables in rules or
include decisions; pipeline validation can occur before
those variables are available.
This is a scoping demonstration, not a secrets lab. Chapter 7 already established that sensitive data needs stronger lifecycle controls.
10. Challenge: choose the correct layer
The deploy job is green, GitLab shows deployment 2 as successful, but the target receipt still reports digest-A. Which layer should you change first?
Do not add retry to YAML. Preserve the deployment/job IDs and inspect the deployment script/provider/target path. The mismatch proves the external-state transition failed or wrote somewhere else; runner retry is not evidence.
11. Cleanup / rollback for the disposable lab
- Close/delete only the throwaway branch/project you created.
- If you keep the project for the checkpoint, retain the environment history and let the one-week artifacts expire naturally.
- If you stop/delete the disposable environment, first record the final deployment IDs and target evidence; note that stopping/deleting GitLab metadata is not proof of external deletion.
- Do not delete shared environments, project-wide variables, unrelated artifacts, or deployment history to make the lab look clean.
12. What you proved
You built one artifact per source revision, transferred it to deployment without rebuilding, created repeated deployment events under one stable environment, and used a separate verify action to inspect target evidence. The next lesson decides how to design these identities and lifecycle controls for larger systems.
Knowledge check
Why does deploy:training use needs:artifacts from build:release?
To consume the exact artifact produced by the build job rather than rebuilding during deployment.
Why does verify:training use environment:action: verify?
It verifies the environment without creating an ordinary new deployment record.
What should stay stable across the two lab pipeline runs?
The logical environment name training/ch19; source SHA, pipeline/job IDs, deployment ID, and artifact digest can change.
Why is the .invalid URL useful in the training lab?
It proves that GitLab URL metadata is independent from deployment success and actual service health.
What is the first diagnostic clue if the target digest remains old after a green deploy job?
The external/deployment layer is inconsistent; preserve evidence and inspect the target path/provider rather than blindly retrying.
Version and compatibility note
GitLab and GitLab Runner evolve continuously. Treat version-sensitive YAML, runner/executor behavior, APIs, security features, policy controls, tiers, and deprecations as assumptions to verify against the current official GitLab documentation before production use. Preserve the exact project/ref/SHA, compiled configuration, pipeline/job IDs, runner/tool versions, and external target evidence used for any reproducible lab or incident record.
Official references and version notes
Documentation verification date: 2026-09-12. Environment/deployment keywords, environment actions, deployment-tier variable support, auto-stop behavior, rollback UI behavior, environment-scoped variables, and deployment safety are version-sensitive. Re-check the GitLab and Runner versions used by production delivery before applying the exact examples.
- Environments — environment identity/state, tiers, URLs, stop/auto-stop behavior, environment-scoped variables, and operational views.
- Deployments — deployment history, deployment refs, retry/rollback behavior, and auditability.
- Deployment safety — deployment serialization, outdated-job prevention, protected environments, and rollback considerations.
-
CI/CD YAML syntax reference
—
environment,environment:name,url,action,auto_stop_in,deployment_tier, and related job semantics. - Deployments API — deployment IDs, statuses, SHA/ref, user, environment, and history queries.
- Environments API — environment metadata/state/tier management when API access is appropriate.
- CI/CD variables — environment scope behavior and precedence boundaries.
Current assumptions used in this chapter:
environments and deployment history are available on Free, Premium,
and Ultimate across GitLab.com, Self-Managed, and Dedicated.
Environment states include available,
stopping, and stopped.
environment:action currently supports
start (default, creates a deployment after job start),
prepare, verify, access, and
stop; the latter four model lifecycle/access work
rather than ordinary deployment creation. Deployment tiers are
production, staging, testing,
development, and other; CI/CD-variable
support for deployment_tier was added in GitLab 18.5.
auto_stop_in uses natural-language durations; stop
processing is background work and is not an exact real-time timer.
The mandatory labs use synthetic artifacts, a stable
training/ch19 environment, alpine:3.22, a
reserved .invalid URL, and filesystem/artifact
snapshots as a faithful target simulation—no cloud account,
protected production environment, or real credential is required.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.