Skip to content

MosheAzraf/simple-crud-ci-cd

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

48 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

MosheAzraf Todo — Client & Server (CI/CD Demo)

A small, working CI/CD example that builds a React/Vite client and a .NET 6 API, pushes Docker images to GitLab Container Registry, and deploys them with a local GitLab Runner + Docker Compose.

build status main

GitLab pipeline status

build status dev

GitLab pipeline status


Purpose

  • Learn and demonstrate the simplest possible CI/CD.
  • Keep a clear split between build (CI) and deploy (CD).
  • Use Docker multi-arch builds, GitLab Container Registry, and a local Runner to deploy with Docker Compose using a single TAG (dev / main).

Architecture

  • Client: Vite/React (Node 20). Built as static assets and served by Nginx.
    Build-time argument: VITE_SERVER_API (in CI set to /api/todo).

  • Server: .NET 6 API listening on :8080 (ASPNETCORE_URLS=http://0.0.0.0:8080).

  • Docker: Client Dockerfile (build → nginx), Server Dockerfile (.NET 6).

  • Deploy: /opt/app/docker-compose.registry.yml on the server selects images by ${TAG} and pulls fresh images each deploy.

  • GitLab CI (.gitlab-ci.yml): Stages build → docker → deploy. Builds and pushes images to the registry, then deploys via a local Runner (shell executor, tag deploy-local).


Repository Layout

.
├─ client/                 # Vite/React app (Dockerfile inside)
├─ Server/
│  └─ Server/              # .NET 6 API (Dockerfile inside)
├─ docker-compose.yml      # optional local dev compose
├─ docker-compose.registry.yml  # used on the server for CD
└─ .gitlab-ci.yml          # build, push, deploy

Note: The production compose for CD lives on the server at /opt/app/docker-compose.registry.yml. Its content is shown below for reference.


Requirements

  • Docker & Docker Compose v2
  • (Maintainers) GitLab Runner installed on the server, registered for this project with tag deploy-local, and member of the docker group.

Run Locally (choose one)

A) From source (local build)

git clone <REPO_URL>
cd MosheAzraf-project
docker compose up -d --build
# Client: http://localhost:3000 | API: http://localhost:8080

B) Using prebuilt images from GitLab Registry

# Pick a tag (dev or main) without creating a file:
TAG=dev docker compose -f docker-compose.registry.yml up -d
# Client: http://localhost:3000

If the project is private, run docker login registry.gitlab.com first.

C) Dev mode without Docker

API

cd Server/Server
dotnet restore
dotnet run --urls http://0.0.0.0:8080

Client

cd client
npm ci
VITE_SERVER_API=http://localhost:8080/api/todo npm run dev
# Opens on http://localhost:5173

CI/CD Overview

Build & Push (CI)

  • Pushing to dev produces:
    • registry.gitlab.com/mosheazraf-group/mosheazraf-project:dev
    • registry.gitlab.com/mosheazraf-group/mosheazraf-project/client:dev
  • Pushing to main produces the same with :main.
  • Images are multi-arch (linux/amd64, linux/arm64) via docker buildx.

Deploy (CD)

  • Local GitLab Runner (shell, tag deploy-local) runs the deploy jobs on the server.
  • Each deploy job writes the tag to /opt/app/.env (TAG=dev or TAG=main) and then runs:
    docker compose --env-file /opt/app/.env -f /opt/app/docker-compose.registry.yml pull
    docker compose --env-file /opt/app/.env -f /opt/app/docker-compose.registry.yml up -d

Compose Used on the Server (CD)

File: /opt/app/docker-compose.registry.yml

services:
  api:
    image: registry.gitlab.com/mosheazraf-group/mosheazraf-project:${TAG}
    container_name: todo-server
    ports:
      - "8080:8080"
    environment:
      ASPNETCORE_URLS: http://0.0.0.0:8080
    volumes:
      - todo_data:/data
    restart: unless-stopped
    pull_policy: always

  client:
    image: registry.gitlab.com/mosheazraf-group/mosheazraf-project/client:${TAG}
    container_name: todo-client
    ports:
      - "3000:80"
    depends_on:
      - api
    restart: unless-stopped
    pull_policy: always

volumes:
  todo_data:

URLs

  • Client: http://<host>:3000
  • API base: http://<host>:8080
  • Todos endpoint: http://<host>:8080/api/todo

Quick Checks

# What is running?
docker ps

# Which image/tag is live?
docker inspect todo-client --format '{{.Config.Image}}'
docker inspect todo-server --format '{{.Config.Image}}'

# Logs
docker logs -n 100 todo-client
docker logs -n 100 todo-server

Troubleshooting

  • Job stuck on “pending” — Ensure the Runner is registered for this project and has tag_list = ["deploy-local"] in /etc/gitlab-runner/config.toml.
  • manifest unknown when pulling — The requested tag doesn’t exist yet. Push to the matching branch so CI builds and pushes that tag.
  • Cannot write /opt/app/.env — The Runner user (gitlab-runner) must be able to write to /opt/app (e.g., chown -R gitlab-runner:gitlab-runner /opt/app).

Why This Project?

To demonstrate a clean, minimal CI/CD:

  • CI builds & pushes images.
  • CD deploys with a single TAG switch.
  • Real, production-like mechanics with minimal moving parts (images, registry, runner, compose).

About

A simple CRUD application that I made to help me learn how to configure and run CI / CD.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published