Issues, Labels, Milestones, Iterations, Boards, Epics, and Work Planning: Guided Hands-On Workflow and Core Operations
Create a disposable GitLab Free backlog, classify it with labels and a milestone, operate a basic board, connect one issue to a branch and merge request, and verify state through UI, glab, Git, and REST evidence.
Learning objectives
- Create a small synthetic backlog in a disposable GitLab Free project using safe, reproducible labels, a milestone, and issues.
- Build or inspect a basic issue board and prove that card movement changes underlying issue metadata rather than creating duplicate state.
- Use glab and the Issues/Boards REST APIs to inspect structured planning state with explicit project scope and pagination awareness.
- Connect one issue to a Git branch and a minimal merge request without requiring CI, paid features, or a production merge.
- Predict before/after state, verify the change independently, and leave a clean evidence trail for the checkpoint lab.
1. Scenario and preflight
You are planning a tiny fictional release for the disposable project used in earlier chapters. The mandatory path uses GitLab Free, one project you own, ordinary labels, one project milestone, one project board, three synthetic issues, and one short-lived branch. Iterations, scoped labels, configurable status, and epics are optional read-only/fixture extensions.
-
Required tools: Git,
glab, andjq(or equivalent JSON inspection). - Required role: enough permission to create/edit issues and planning metadata in the disposable project. Current GitLab documents Planner/Reporter/Developer/Maintainer/Owner as relevant planning roles for many issue/board operations; exact mutation permissions vary by operation.
- No runner, pipeline, registry, cloud, Kubernetes, paid subscription, or extra account is required.
-
Do not use a real product backlog. Prefix synthetic items with
[CH05 LAB].
2. Inspect host, project, default branch, and current backlog first
Before changing anything, capture the target. The default branch matters later because issue-closing patterns take effect when the referenced change reaches that branch.
glab auth status
glab repo view --output json --jq '{path_with_namespace,visibility,default_branch,web_url}'
git remote -v
git status --short --branch
glab issue list --all --output json --jq '.[] | {iid,title,state,labels,milestone}'
Stop if the repository or GitLab host is not the disposable target. This is the same target-confirmation discipline used for credentials and namespaces in earlier chapters.
3. Define the metadata policy before creating items
Create the vocabulary on paper first. A small policy prevents the “label everything” anti-pattern.
| Dimension | Lab convention | Owner/rule |
|---|---|---|
| Work type |
type-bug, type-docs,
type-feature
|
Exactly one type label by team convention; Free does not enforce exclusivity. |
| Flow | flow-ready, flow-doing |
At most one flow label by convention; board lists visualize it. |
| Delivery goal | Milestone CH05 Demo |
One milestone for the miniature release slice. |
| Ownership | Assignee only when someone is actively accountable | Do not create labels that duplicate usernames. |
| Priority | Omitted from this lab | Add only when the team can define consistent semantics. |
4. Create ordinary labels safely and verify them
The Project Labels API is Free. Use distinct colors only for readability; color has no authorization or workflow semantics.
PROJECT_ID="123456" # replace with your disposable project ID
# First inspect existing labels to avoid duplicate names.
glab api "projects/$PROJECT_ID/labels?per_page=100" --paginate --jq '.[] | {id,name,color,description}'
# Create only if these synthetic labels do not already exist.
glab api "projects/$PROJECT_ID/labels" -X POST -f name='type-bug' -f color='#D9534F' -f description='CH05 lab: defect work'
glab api "projects/$PROJECT_ID/labels" -X POST -f name='type-docs' -f color='#428BCA' -f description='CH05 lab: documentation work'
glab api "projects/$PROJECT_ID/labels" -X POST -f name='type-feature' -f color='#5CB85C' -f description='CH05 lab: feature work'
glab api "projects/$PROJECT_ID/labels" -X POST -f name='flow-ready' -f color='#F0AD4E' -f description='CH05 lab: ready for implementation'
glab api "projects/$PROJECT_ID/labels" -X POST -f name='flow-doing' -f color='#8E44AD' -f description='CH05 lab: active implementation'
5. Create a project milestone and understand its scope
A milestone is a delivery grouping, not a board column. The project milestone in this lab cannot automatically coordinate sibling projects. A group milestone would be the appropriate shared resource when several descendant projects must participate.
# Inspect first.
glab api "projects/$PROJECT_ID/milestones?state=active&per_page=100" --paginate --jq '.[] | {id,iid,title,state,start_date,due_date}'
# Create the synthetic milestone if it does not already exist.
glab api "projects/$PROJECT_ID/milestones" -X POST -f title='CH05 Demo' -f description='Disposable planning milestone for Chapter 05'
6. Create a three-item backlog and inspect structured state
Use issue titles that clearly identify the lab. The first issue is the delivery slice you will connect to Git; the other two make the board useful enough to reason about.
REPO="group/example-project" # replace with your disposable path
glab issue create -R "$REPO" -t '[CH05 LAB] Fix parser edge case' -d 'Synthetic defect used to demonstrate planning metadata.' -l type-bug,flow-ready -m 'CH05 Demo' --yes
glab issue create -R "$REPO" -t '[CH05 LAB] Add operator note' -d 'Synthetic documentation task.' -l type-docs,flow-ready -m 'CH05 Demo' --yes
glab issue create -R "$REPO" -t '[CH05 LAB] Add dry-run option' -d 'Synthetic feature item.' -l type-feature,flow-ready -m 'CH05 Demo' --yes
# Structured inspection. Capture the IIDs.
glab api "projects/$PROJECT_ID/issues?scope=all&state=opened&labels=flow-ready&per_page=100" --paginate --jq '.[] | {iid,title,labels,milestone:(.milestone.title // null),assignees:[.assignees[].username]}'
Expected state: three open issues, each with one type label,
flow-ready, and milestone CH05 Demo. If a
label is missing, repair the issue metadata rather than “fixing the
board.”
7. Create or inspect a basic board, then prove card movement
Current glab includes
glab issue board create. Create a project board named
CH05 Flow if one does not exist. Add label lists for
flow-ready and flow-doing through the
current board UI (Plan → Issue boards) or the
Boards API. The UI label may move in future releases, so the
resource/API is the stable anchor.
glab issue board create 'CH05 Flow' -R "$REPO"
# Read-only board evidence.
glab api "projects/$PROJECT_ID/boards" --paginate --jq '.[] | {id,name,lists:[.lists[]? | {id,label:(.label.name // null),position}]}'
Now choose the parser issue. Prediction: moving it
from the flow-ready label list to
flow-doing should alter the issue’s labels. Perform the
drag in the board UI, then verify independently:
ISSUE_IID="1" # replace with the parser issue IID
glab api "projects/$PROJECT_ID/issues/$ISSUE_IID" --jq '{iid,title,state,labels,milestone:(.milestone.title // null)}'
If the result still contains flow-ready instead of
flow-doing, do not repeat the drag blindly. Inspect the
board list type/configuration and confirm you moved the correct card
on the correct board.
8. Connect one work item to Git and a merge request
This chapter does not replace the later merge-request chapter. The goal here is only to prove that planning state and code-delivery state are separate objects with explicit links.
Use the issue IID in the branch name. GitLab can associate an issue/task when a branch starts with the item number. Make a harmless documentation-only change so the lab does not require application knowledge.
git switch -c "$ISSUE_IID-ch05-parser-note"
printf '
Chapter 05 planning lab marker.
' >> CH05_LAB.md
git add CH05_LAB.md
git commit -m "docs: add chapter 05 planning marker"
git push -u origin HEAD
# Create a draft MR that references the work item but does not merge it yet.
glab mr create -R "$REPO" --source-branch "$ISSUE_IID-ch05-parser-note" --title '[CH05 LAB] Parser planning slice' --description "Closes #$ISSUE_IID
Disposable Chapter 05 planning exercise." --draft --yes
glab mr list -R "$REPO" --output json --jq '.[] | select(.title | startswith("[CH05 LAB]")) | {iid,title,state,source_branch,target_branch}'
Expected state: the issue remains open because the
draft MR has not merged into the project’s default branch. The MR
description expresses a future closing relationship. This is a
crucial distinction: writing Closes #N is not itself
the close event.
9. Optional paid-feature tour without requiring purchase
If your namespace has Premium/Ultimate, inspect—but do not require—these richer fields:
- Scoped labels: compare mutual exclusion with the lab’s convention-only ordinary labels.
- Iteration: inspect the project’s ancestor-group iterations and identify cadence/start/end date.
- Status: compare explicit work-item status with Free open/closed + flow labels.
- Epic: use the current Work items interface/API rather than new code against the deprecated Epics REST API.
On Free, create a local JSON fixture containing
{"iteration":"Sprint 12","epic":"Checkout reliability"}
and explain what those fields would mean. Do not label the fixture
as live GitLab state.
10. Challenge: choose the correct control
Your team asks for three changes: “mark the parser issue as actively worked,” “put all three items in the same release goal,” and “show the items on a board.” Choose the control before touching the UI:
-
Active work → change the workflow metadata (
flow-doingon the Free path). - Release goal → milestone.
- Visual workflow → board configured to project those metadata values.
If your answer was “create three more boards,” the mental model is backwards. The metadata is the source state; boards are views.
Knowledge check
After a card moves to the Doing list, what is the best independent verification?
Read the issue directly or query the Issues API and verify its workflow label/status changed as predicted.
Why does the draft MR not immediately close the issue even though its description says Closes #N?
Closing patterns take effect when the qualifying commit or MR reaches the project default branch; merely creating the MR records the relationship.
Why inspect labels and milestones before POSTing them?
To avoid duplicate/partial state and to make automation idempotent. Blind retries of mutations can create unintended resources.
Does creating a project board create new copies of the three issues?
No. The board renders the existing issues as cards and groups them according to metadata/list configuration.
If the team needs recurring two-week sprints on GitLab Free, should the lesson claim milestones are iterations?
No. You can simulate sprint intent with a milestone or fixture, but must label that iterations are a distinct Premium/Ultimate group-level feature.
Summary
You created the free-compatible planning core: ordinary labels, one project milestone, three issues, a board, a branch, and a draft merge request. Every important step was verified from the underlying issue/project state instead of trusting one visual view. The next lesson focuses on choosing which planning dimensions belong in a maintainable operating model.
Official references
- GitLab Docs — Work items
- GitLab Docs — Manage issues
- GitLab Docs — Labels
- GitLab Docs — Milestones
- GitLab Docs — Iterations
- GitLab Docs — Issue boards
- GitLab Docs — Manage epics
- GitLab Docs — Issues API
- GitLab Docs — Project issue boards API
- GitLab Docs — REST API pagination
- GitLab Docs — glab issue
- GitLab Docs — glab work-items list
- GitLab Docs — glab mr create
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.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this
address.