Tools: GitHub Actions CI/CD for Beginners: Deploy Your App Automatically in 2026
What Is CI/CD?
Your First Workflow: Run Tests on Every Push
Deploy to Vercel on Merge
Deploy a Docker App to Railway
Useful Workflow Patterns
Matrix Testing (Multiple Node Versions)
Cache Dependencies
Run Only When Specific Files Change
Scheduled Workflows (Cron)
Setting Up Secrets
Common Mistakes
My Recommended Starter Setup You push code, tests run automatically, and your app deploys itself. That's CI/CD, and GitHub Actions makes it free and surprisingly easy. GitHub Actions gives you 2,000 free minutes/month on public repos (and 500 on private). Create .github/workflows/test.yml: That's it. Push this file and GitHub will run your tests on every push and PR. Never put secrets directly in workflow files. For most projects, you need just two workflows: Start simple, add complexity only when you need it. More developer guides at lucasmdevdev.github.io Templates let you quickly answer FAQs or store snippets for re-use. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse
name: Run Tests on: push: branches: [main] pull_request: branches: [main] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' - name: Install dependencies run: npm ci - name: Run tests run: npm test - name: Run linter run: npm run lint
name: Run Tests on: push: branches: [main] pull_request: branches: [main] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' - name: Install dependencies run: npm ci - name: Run tests run: npm test - name: Run linter run: npm run lint
name: Run Tests on: push: branches: [main] pull_request: branches: [main] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' - name: Install dependencies run: npm ci - name: Run tests run: npm test - name: Run linter run: npm run lint
name: Deploy to Vercel on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Deploy to Vercel uses: amondnet/vercel-action@v25 with: vercel-token: ${{ secrets.VERCEL_TOKEN }} vercel-org-id: ${{ secrets.VERCEL_ORG_ID }} vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }} vercel-args: '--prod'
name: Deploy to Vercel on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Deploy to Vercel uses: amondnet/vercel-action@v25 with: vercel-token: ${{ secrets.VERCEL_TOKEN }} vercel-org-id: ${{ secrets.VERCEL_ORG_ID }} vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }} vercel-args: '--prod'
name: Deploy to Vercel on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Deploy to Vercel uses: amondnet/vercel-action@v25 with: vercel-token: ${{ secrets.VERCEL_TOKEN }} vercel-org-id: ${{ secrets.VERCEL_ORG_ID }} vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }} vercel-args: '--prod'
name: Deploy to Railway on: push: branches: [main] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: npm ci && npm test deploy: needs: test # Only deploy if tests pass runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install Railway CLI run: npm install -g @railway/cli - name: Deploy run: railway up env: RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}
name: Deploy to Railway on: push: branches: [main] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: npm ci && npm test deploy: needs: test # Only deploy if tests pass runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install Railway CLI run: npm install -g @railway/cli - name: Deploy run: railway up env: RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}
name: Deploy to Railway on: push: branches: [main] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: npm ci && npm test deploy: needs: test # Only deploy if tests pass runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install Railway CLI run: npm install -g @railway/cli - name: Deploy run: railway up env: RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}
strategy: matrix: node-version: [18, 20, 22] steps: - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }}
strategy: matrix: node-version: [18, 20, 22] steps: - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }}
strategy: matrix: node-version: [18, 20, 22] steps: - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }}
- uses: actions/cache@v4 with: path: ~/.npm key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
- uses: actions/cache@v4 with: path: ~/.npm key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
- uses: actions/cache@v4 with: path: ~/.npm key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
on: push: paths: - 'src/**' - 'package.json'
on: push: paths: - 'src/**' - 'package.json'
on: push: paths: - 'src/**' - 'package.json'
on: schedule: - cron: '0 9 * * 1' # Every Monday at 9 AM UTC
on: schedule: - cron: '0 9 * * 1' # Every Monday at 9 AM UTC
on: schedule: - cron: '0 9 * * 1' # Every Monday at 9 AM UTC - CI (Continuous Integration): Automatically run tests when you push code
- CD (Continuous Deployment): Automatically deploy when tests pass - Go to your repo > Settings > Secrets and variables > Actions
- Click "New repository secret"
- Add your secret (e.g., VERCEL_TOKEN) - Not using npm ci — Use npm ci instead of npm install in CI (it's faster and more deterministic)
- Forgetting to cache — Caching node_modules or ~/.npm saves 30-60 seconds per run
- Running everything on push — Use path filters to avoid running tests when you only changed docs
- Not setting timeout — Add timeout-minutes: 10 to prevent stuck workflows from eating your minutes - test.yml — Runs on every push/PR: lint + test
- deploy.yml — Runs on push to main (after tests pass): deploy to production