← Blog

GitHub Actions for Beginners: Create Your First Workflow

October 17, 2026

When I first started learning GitHub Actions, I struggled with two questions: what is a workflow, and how do I create one? The YAML examples were short, but it wasn't obvious what each line actually did.

In this tutorial, we'll create a workflow that prints a greeting and the current date whenever you push code to GitHub. Then we'll deliberately break it so you can see how a failed step appears in the logs.

This is the written companion to my first GitHub Actions workflow video. You can follow the entire tutorial here without watching it. The source code is in HaiderMalik12/github-actions-journey.

What is GitHub Actions?

GitHub Actions lets you automate work in your repository. You describe that work in a workflow, stored as a YAML file. An event, such as pushing a commit, starts the workflow.

A workflow contains jobs. Each job runs on a runner, which is the machine that executes its steps. For this tutorial, the steps are ordinary terminal commands.

Here's the sequence we'll build:

Push a commit
  → Start the Hello Actions workflow
    → Run the say-hello job on Ubuntu
      → Print a greeting
      → Print the current date

Later, those commands could install dependencies, run tests, or build your application. First, let's get a small workflow running and understand its parts.

1. Create and clone a repository

You'll need a GitHub account, Git installed locally, and a code editor. I'll use VS Code.

On GitHub, create a new repository named github-actions-journey, or choose your own name. Select Public, enable Add a README file, and create the repository.

Copy its clone URL from the Code menu. In your terminal, run the following commands, replacing YOUR_USERNAME with your GitHub username:

git clone https://github.com/YOUR_USERNAME/github-actions-journey.git
cd github-actions-journey
code .

If the code command isn't available, open the cloned folder directly from your editor.

The repository's Actions tab is where you'll inspect your workflow runs once you've pushed the file.

2. Create the workflow file

At the root of the repository, create this folder structure:

github-actions-journey/
├── .github/
│   └── workflows/
│       └── hello.yml
└── README.md

The location matters: GitHub discovers workflow files inside .github/workflows/. Both .yml and .yaml extensions work.

Add this to hello.yml:

name: Hello Actions
 
on: push
 
jobs:
  say-hello:
    runs-on: ubuntu-latest
    steps:
      - run: echo "Hello, GitHub Actions!"

This is enough for our first run. Let's walk through it before pushing.

Give the workflow a name

name: Hello Actions

name is a YAML key, and Hello Actions is its value. This name appears in GitHub's Actions tab so you can identify the workflow.

YAML uses indentation to express nesting. Use spaces consistently; the examples here use two spaces for each level. A misplaced line can change the structure or make the workflow invalid.

Choose the event that starts it

on: push

on defines the trigger. Here, a push starts the workflow. We haven't restricted it to a particular branch, so a normal commit pushed to any branch containing this workflow can trigger it.

You can also listen for more than one event:

on: [push, pull_request]

That's an alternative configuration, not an extra line to add alongside on: push. Keep the single push trigger for this tutorial. GitHub's workflow syntax reference explains the available event configuration forms.

Define a job

jobs:
  say-hello:

jobs contains the jobs in this workflow. say-hello is the job ID we've chosen.

One useful YAML distinction: jobs is a mapping of job IDs to job configurations. It isn't a list. The steps inside our job will be a list, which is why those entries use dashes.

Choose a runner

    runs-on: ubuntu-latest

Our commands need a machine to execute them. runs-on selects the runner; ubuntu-latest requests a GitHub-hosted Ubuntu environment.

Ubuntu is a useful starting point when your application will run on Linux. GitHub also provides Windows and macOS runner options. The latest label can move to a newer supported image over time, so don't treat it as a fixed operating system version.

For this standard Ubuntu runner, each job gets a fresh virtual machine. See GitHub's hosted runner documentation for environment details.

Add a command to the steps list

    steps:
      - run: echo "Hello, GitHub Actions!"

The dash starts one item in the steps list. run tells the runner to execute a shell command, and echo prints the message to the log.

These commands don't read any repository files, so we don't need a checkout step yet. When a later workflow needs your source code, you'll usually add a checkout action before the commands that use it.

3. Commit, push, and inspect the first run

Save the file, then run:

git add .github/workflows/hello.yml
git commit -m "Create first GitHub Actions workflow"
git push

Now open the repository on GitHub:

  1. Select the Actions tab.
  2. Open the run associated with your commit message.
  3. Select the say-hello job.
  4. Expand the step that runs echo.

You should see this output:

Hello, GitHub Actions!

You'll also see runner setup and completion entries that GitHub adds automatically. Once the run finishes successfully, GitHub shows a green check.

Notice the different labels: Hello Actions identifies the workflow, your commit message identifies this push-triggered run, and say-hello identifies the job.

4. Name the step and print the date

Without a step name, GitHub displays a label based on the command. Give your steps names that are easy to recognize in the logs.

Replace the contents of hello.yml with:

name: Hello Actions
 
on: push
 
jobs:
  say-hello:
    runs-on: ubuntu-latest
    steps:
      - name: Say hello
        run: echo "Hello, GitHub Actions updated!"
 
      - name: Show the date
        run: date

This follows the final workflow in the tutorial source file, with the greeting's capitalization cleaned up.

name and run belong to the same step. That's why run lines up with name and doesn't have another dash in front of it. The second dash starts a separate step, Show the date.

The date command prints the current date and time from the runner. Its output reflects the runner's environment, so it may differ from your local time.

Commit and push again:

git add .github/workflows/hello.yml
git commit -m "Name the greeting step and show the date"
git push

Open the new run. Inside say-hello, you'll now see Say hello followed by Show the date, with the output available under each step.

5. Fail a step on purpose

Successful runs are only half the picture. Let's see what happens when a command fails.

Temporarily insert this step between the greeting and the date:

      - name: Fail on purpose
        run: exit 1

Your steps should now look like this:

    steps:
      - name: Say hello
        run: echo "Hello, GitHub Actions updated!"
 
      - name: Fail on purpose
        run: exit 1
 
      - name: Show the date
        run: date

exit 1 ends the shell command with a nonzero exit status, indicating failure. It demonstrates a failure; it doesn't handle one.

Commit and push:

git add .github/workflows/hello.yml
git commit -m "Fail the workflow on purpose"
git push

In this run, the greeting succeeds, Fail on purpose fails, and Show the date is skipped. By default, subsequent ordinary steps require the earlier steps to have succeeded. The job and this workflow run are marked as failed.

Open the failed step's logs. You'll find an error reporting exit code 1. When your real test or build command fails, this is where you'll start investigating too.

Now remove the Fail on purpose step, returning the file to the two-step version above. Save, commit, and push:

git add .github/workflows/hello.yml
git commit -m "Remove intentional failure"
git push

Check the latest run to confirm both steps pass. Fixing the workflow creates a new successful run; the earlier failed run stays in the history.

Common mistakes to check

If your workflow doesn't behave as expected, start with these checks:

  • File location: use .github/workflows/hello.yml, with the leading dot in .github and the plural workflows.
  • Indentation: runs-on and steps belong under say-hello. Each step belongs under steps.
  • List items: use one dash for each new step. A step's name and run share the same item.
  • YAML syntax: keep the colons after keys and close any quotes you open. Not every value needs quotes, but an unfinished quoted string is invalid.
  • Pushing changes: saving locally doesn't trigger on: push. Commit the file and push it to GitHub.
  • The correct run: open the latest commit's run when checking a fix, rather than reopening the earlier failure.

You now have a workflow that responds to a push, starts a job on an Ubuntu runner, and executes two named steps. You've also seen how to find a failed command in the logs. Those same pieces form the foundation for adding automated tests and builds to your projects.