Sign inSign up

ams0/labspace-flux-gitops:latest

Manifest digest

sha256:4fa8a0eb8c09235b0aa027588a3a28451290bd3a013327a79622cf0b36a92744

Last pushed

4 months by ams0

Type

Compose

Manifest digest

sha256:4fa8a0eb8c09235b0aa027588a3a28451290bd3a013327a79622cf0b36a92744

Compose file content

##################################################################################################
# This Compose file is intended to run a Labspace. It can be run with the following:
#
# CONTENT_REPO_URL=https://github.com/username/labspace-content-repo docker compose -f compose.run.yaml up
##################################################################################################

services:
  configurator:
    image: dockersamples/labspace-configurator:v0.26.0
    use_api_socket: true
    volumes:
      - labspace-content:/project
      - labspace-instructions:/instructions
      - docker-creds:/docker-creds
      - labspace-support:/etc/labspace-support

  interface:
    image: dockersamples/labspace-interface:v0.26.0
    ports:
      - "3030:3030"
    volumes:
      - labspace-instructions:/labspace/instructions:ro
      - type: volume
        source: labspace-support
        target: /etc/labspace-support/private-key
        volume:
          subpath: private-key
      - type: volume
        source: labspace-support
        target: /etc/labspace-support/socket
        volume:
          subpath: socket
      - type: volume
        source: labspace-support
        target: /etc/labspace-support/metadata
        volume:
          subpath: metadata
    depends_on:
      workspace:
        condition: service_started
      configurator:
        condition: service_completed_successfully
    restart: unless-stopped

  workspace:
    image: dockersamples/labspace-workspace-node:v0.26.0
    depends_on:
      configurator:
        condition: service_completed_successfully
      socket-proxy:
        condition: service_started
    ports:
      - 8085:8085 # For the IDE itself
      - 3000:3000 # For the Node application running in the IDE
    volumes:
      - socket-proxy:/var/run
      - labspace-content:/home/coder/project
      - docker-creds:/docker-creds
      - type: volume
        source: labspace-support
        target: /etc/labspace-support/public-key
        volume:
          subpath: public-key
      - type: volume
        source: labspace-support
        target: /etc/labspace-support/socket
        volume:
          subpath: socket


  host-republisher:
    image: dockersamples/labspace-host-port-republisher:v0.26.0
    pull_policy: always
    restart: always
    volumes:
      - socket-proxy:/var/run
    network_mode: service:workspace
    environment:
      LABEL_FILTER: labspace-resource=true
    depends_on:
      - workspace
      - socket-proxy

  workspace-cleaner:
    image: dockersamples/labspace-cleaner:v0.26.0
    pull_policy: always
    volumes:
      - socket-proxy:/var/run
    environment:
      LABEL_FILTER: labspace-resource=true
    depends_on:
      - socket-proxy

  socket-proxy:
    image: mikesir87/docker-socket-proxy:v1.3.1
    volumes:
      - socket-proxy:/tmp/proxy
      - /var/run/docker.sock:/var/run/docker.sock
    configs:
      - source: docker-proxy-config-labels
        target: /etc/docker-socket-proxy/config.d/add-and-require-labels.yaml
      - source: docker-proxy-mount-remap
        target: /etc/docker-socket-proxy/config.d/mount-path-remap.yaml
      - source: docker-proxy-mount-allowlist
        target: /etc/docker-socket-proxy/config.d/mount-path-allowlist.yaml
      - source: docker-proxy-forward-proxied-socket
        target: /etc/docker-socket-proxy/config.d/forward-proxied-socket.yaml
      - source: docker-proxy-add-to-labspace-network
        target: /etc/docker-socket-proxy/config.d/add-to-labspace-network.yaml
    environment:
      DEBUG_LOGS: "true"
      LISTEN_SOCKET_PATH: /tmp/proxy/docker.sock

volumes:
  socket-proxy:
    name: labspace-socket-proxy
  labspace-content:
    name: labspace-content
  labspace-instructions:
  docker-creds:
  labspace-support:

networks:
  default:
    name: labspace

configs:
  docker-proxy-config-labels:
    content: |
      mutators:
        # Add labels to all newly created objects
        - type: addLabels
          labels:
            labspace-resource: "true"
      responseFilters:
        # Only return objects with the labels we mutated on
        - type: labelFilter
          objectsToFilter:
            - containers
            - volumes
            - networks
          requiredLabels:
            labspace-resource: "true"
  docker-proxy-mount-remap:
    content: |
      # Remap the project directory to the labspace content volume, since the project
      # is running out of a volume.
      mutators:
        - type: mountPath
          from: /home/coder/project
          to: labspace-content
  docker-proxy-mount-allowlist:
    content: |
      gates:
        - type: mountSource
          allowedSources:
            - labspace-content
            - labspace-socket-proxy
            - buildx_buildkit_default_state
            - label:labspace-resource=true
  docker-proxy-forward-proxied-socket:
    content: |
      # If requests to use the Docker Socket are used (such as Testcontainers),
      # use the proxied one to ensure permissions, remappings, etc. are applied
      mutators:
        - type: mountPath
          from: /var/run/docker.sock
          to: labspace-socket-proxy/docker.sock
  docker-proxy-add-to-labspace-network:
    content: |
      mutators:
        - type: addToNetwork
          networks:
            - labspace

---
#########################################################################
# This Compose file extends the default Labspace setup by adding the following elements:
#
# - A Traefik reverse proxy to route traffic to Gitea and k3s
# - A Gitea server for Git hosting and CI/CD
# - A Gitea self-hosted runner for executing CI jobs
# - A k3s Kubernetes cluster
#
# This provides a "SDLC-in-a-box" environment for end-to-end labs.
#
# Additional documentation is found in the docs/sdlc-labspace.md file.
#########################################################################

services:
  ############################################################
  # Overrides to Labspace default services
  ############################################################
  workspace:
    environment:
      SKIP_CI_SECRET_SETUP: ${SKIP_CI_SECRET_SETUP:-false}
    volumes:
      - gitea-outputs:/gitea-outputs:ro
      - k3s-output:/kubeconfig:ro
      - type: volume
        source: labspace-support
        target: /etc/labspace-support/k8s-setup
        volume:
          subpath: k8s-setup
    configs:
      - source: workspace-entrypoint-script
        target: /entrypoint.d/50-entrypoint-script.sh
        mode: 0755
      - source: workspace-wait-for-bootstrap
        target: /startup-scripts/00-wait-for-bootstrap.sh
        mode: 0755
      - source: workspace-ci-secrets
        target: /startup-scripts/10-ci-secrets.sh
        mode: 0755
      - source: workspace-kubeconfig-setup
        target: /startup-scripts/10-kubeconfig-setup.sh
        mode: 0755
      - source: workspace-git-setup
        target: /startup-scripts/20-git-setup.sh
        mode: 0755
      - source: workspace-configure-k3s-coredns
        target: /startup-scripts/15-configure-k3s-coredns.sh
        mode: 0755
    depends_on:
      git:
        condition: service_healthy
      k3s:
        condition: service_healthy
      zone-file-creator:
        condition: service_completed_successfully

  interface:
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.interface.rule=Host(`dockerlabs.xyz`)"
      - "traefik.http.routers.interface.entrypoints=web"
      - "traefik.http.routers.interface.service=interface"
      - "traefik.http.services.interface.loadbalancer.server.port=3030"

  ############################################################
  # New services
  ############################################################
  
  cert-creator:
    image: alpine
    command: /generate-certs.sh
    configs:
      - source: generate-traefik-certs
        target: /generate-certs.sh
        mode: 0755
    volumes:
      - labspace-traefik-certs:/certs

  zone-file-creator:
    image: dhi.io/wbitt-network-multitool:3-alpine3.23-dev
    command: /create-zone-file.sh
    configs:
      - source: create-zone-file
        target: /create-zone-file.sh
        mode: 0755
    volumes:
      - labspace-support:/labspace-support
    depends_on:
      traefik:
        condition: service_started 

  traefik:
    image: traefik:3.6.13
    command:
      - --api.insecure=true
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
      - --entrypoints.web.address=:80
      - --entrypoints.ssh.address=:22
      - --entrypoints.websecure.address=:443
      - --entrypoints.websecure.http.tls=true
      - --providers.file.filename=/etc/traefik/cert-config.yaml
    ports:
      - "22:22"
      - "80:80"
      - "443:443"
      - "8080:8080"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - labspace-traefik-certs:/certs:ro
    configs:
      - source: traefik-cert-config
        target: /etc/traefik/cert-config.yaml
    networks:
      default:
        aliases:
          - git.dockerlabs.xyz
          - registry.dockerlabs.xyz
          - app.dockerlabs.xyz
    depends_on:
      cert-creator:
        condition: service_completed_successfully

  registry:
    image: ghcr.io/project-zot/zot:v2.1.15
    labels:
      traefik.enable: true
      traefik.http.routers.registry.rule: Host(`registry.dockerlabs.xyz`)
      traefik.http.services.registry.loadbalancer.server.port: "5000"
      traefik.http.middlewares.remove-frame-headers.headers.customresponseheaders.X-Frame-Options: ""
      traefik.http.routers.registry.middlewares: remove-frame-headers

  git:
    image: gitea/gitea:1.25.5
    volumes:
      - gitea-data:/data
      - gitea-outputs:/gitea-outputs
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    configs:
      - source: gitea-bootstrap
        target: /etc/gitea/bootstrap.sh
        mode: 0755
    environment:
      GITEA__server__ROOT_URL: http://git.dockerlabs.xyz/
      GITEA__server__SSH_DOMAIN: git.dockerlabs.xyz
      GITEA__server__SSH_LISTEN_PORT: 22
      GITEA__database__DB_TYPE: sqlite3

      # This prevents the first-time install page from showing (setup is done via script/env vars)
      GITEA__security__INSTALL_LOCK: "true"
    labels:
      traefik.enable: "true"
      traefik.http.routers.git.rule: Host(`git.dockerlabs.xyz`)
      traefik.http.routers.git.entrypoints: web
      traefik.http.routers.git.service: git
      traefik.http.services.git.loadbalancer.server.port: "3000"
      traefik.http.middlewares.remove-frame-headers.headers.customresponseheaders.X-Frame-Options: ""
      traefik.http.routers.git.middlewares: remove-frame-headers
      traefik.http.routers.git-secure.rule: Host(`git.dockerlabs.xyz`)
      traefik.http.routers.git-secure.entrypoints: websecure
      traefik.http.routers.git-secure.service: git
      traefik.tcp.routers.git-ssh.rule: HostSNI(`*`)
      traefik.tcp.routers.git-ssh.entrypoints: ssh
      traefik.tcp.routers.git-ssh.service: git-ssh
      traefik.tcp.services.git-ssh.loadbalancer.server.port: "22"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/api/healthz"]
      interval: 10s
      timeout: 10s
      retries: 3
      start_period: 5s
    post_start:
      - command: bash /etc/gitea/bootstrap.sh
        user: root

  gitea-runner:
    image: gitea/act_runner:0.3.1
    entrypoint: []
    command: bash -c "GITEA_RUNNER_REGISTRATION_TOKEN=$(cat /gitea-outputs/runner_token.txt) /sbin/tini -- run.sh"
    volumes:
      - type: volume
        source: socket-proxy
        target: /var/run/docker.sock
        volume:
          subpath: docker.sock
      - gitea-outputs:/gitea-outputs
    environment:
      GITEA_INSTANCE_URL: http://git.dockerlabs.xyz
      GITEA_RUNNER_NAME: labspace-runner

      # The runner uses 1.41 Docker API by default, but we need 1.45+ when using the socket proxy
      # due to volume rewrites using volume subpaths
      DOCKER_API_VERSION: "1.45"
    depends_on:
      git:
        condition: service_healthy

  k3s:
    # When updating the k8s version, ensure the kubectl version in the base workspace image is updated
    image: rancher/k3s:v1.35.3-k3s1
    command: server --tls-san k8s.dockerlabs.xyz
    privileged: true
    volumes:
      - k3s-server:/var/lib/rancher/k3s
      - k3s-output:/output
    environment:
      - K3S_TOKEN=labspace-k3s-token
      - K3S_KUBECONFIG_OUTPUT=/output/kubeconfig.yaml
      - K3S_KUBECONFIG_MODE=666
    tmpfs:
      - /run
      - /var/run
    ulimits:
      nproc: 65535
      nofile:
        soft: 65535
        hard: 65535
    networks:
      default:
        aliases:
          - k8s.dockerlabs.xyz
    configs:
      - source: k3s-registry-config
        target: /etc/rancher/k3s/registries.yaml
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.prod-app.rule=Host(`app.dockerlabs.xyz`)"
      - "traefik.http.routers.prod-app.entrypoints=web"
      - "traefik.http.routers.prod-app.service=prod-app"
      - "traefik.http.services.prod-app.loadbalancer.server.port=80"
    healthcheck:
      test: ["CMD", "kubectl", "get", "--raw", "/readyz"]
      interval: 10s
      timeout: 10s
      retries: 10
      start_period: 20s


volumes:
  gitea-data:
  gitea-outputs:
  k3s-server:
  k3s-output: 
  labspace-traefik-certs:


configs:
  # Generate the certs for Traefik to use
  generate-traefik-certs:
    content: |
      #!/bin/sh

      apk add --no-cache openssl >/dev/null 2>&1 || true;
      mkdir -p /certs/ca

      if [ -f /certs/dockerlabs.crt ] && [ -f /certs/dockerlabs.key ]; then
        echo 'Certs already exist, skipping generation';
        return
      fi

      # Create CA (one-time)
      if [ ! -f /certs/ca/ca.crt ] || [ ! -f /certs/ca/ca.key ]; then
        openssl genrsa -out /certs/ca/ca.key 4096
        openssl req -x509 -new -nodes -key /certs/ca/ca.key -sha256 -days 3650 \
          -subj "/CN=dockerlabs-local-ca" \
          -out /certs/ca/ca.crt
      fi

      # Create leaf key + CSR
      openssl genrsa -out /certs/dockerlabs.key 4096
      cat > /tmp/leaf.cnf <<'CNF'
      [req]
      distinguished_name = req_distinguished_name
      req_extensions = v3_req
      prompt = no

      [req_distinguished_name]
      CN = dockerlabs.xyz

      [v3_req]
      subjectAltName = @alt_names
      keyUsage = critical, digitalSignature, keyEncipherment
      extendedKeyUsage = serverAuth

      [alt_names]
      DNS.1 = dockerlabs.xyz
      DNS.2 = *.dockerlabs.xyz
      CNF

      openssl req -new -key /certs/dockerlabs.key -out /tmp/dockerlabs.csr -config /tmp/leaf.cnf

      # Sign leaf cert with CA
      openssl x509 -req -in /tmp/dockerlabs.csr \
        -CA /certs/ca/ca.crt -CAkey /certs/ca/ca.key -CAcreateserial \
        -out /certs/dockerlabs.crt -days 825 -sha256 \
        -extfile /tmp/leaf.cnf -extensions v3_req

      # combine cert & key into a PEM (optional)
      cat /certs/dockerlabs.crt /certs/dockerlabs.key > /certs/dockerlabs.pem || true;

      echo 'Generated CA and certificates';
      find /certs -type f

  # Traefik configuration to use the generated certs
  traefik-cert-config:
    content: |
      tls:
        certificates:
          - certFile: /certs/dockerlabs.crt
            keyFile: /certs/dockerlabs.key
        stores:
          default:
            defaultCertificate:
              certFile: /certs/dockerlabs.crt
              keyFile: /certs/dockerlabs.key

  # Override the default allow list to add buildkit builders and the gitea volumes
  docker-proxy-mount-allowlist:
    content: |
      gates:
        - type: mountSource
          allowedSources:
            - labspace-content
            - labspace-socket-proxy
            - buildx_buildkit_default_state
            - buildx_buildkit_builder-*
            - label:labspace-resource=true
            - act-toolcache
            - GITEA*

  # The /entrypoint.d hook in code-server does not guarantee order of execution, as it simply uses `find` to get files in the 
  # directory. To ensure our scripts run in the correct order, we prefix them with numbers and use a custom entrypoint script 
  # to sort and execute the scripts in order.
  workspace-entrypoint-script:
    content: |
      #!/bin/sh
      set -eu

      CUSTOM_DIR="/startup-scripts"

      # Nothing to do if the directory doesn't exist
      [ -d "$$CUSTOM_DIR" ] || exit 0

      # Find executable regular files, sort them, and run one by one
      find "$$CUSTOM_DIR" -type f -executable -print \
        | sort \
        | while IFS= read -r script; do
            echo "→ Running $$script"
            "$$script"
          done

  # Waits for the Gitea bootstrap to complete before allowing other startup scripts to proceed.
  # Validates that the moby user exists and the SSH key is registered, which are the last steps
  # of the bootstrap and gate everything the workspace startup scripts depend on.
  workspace-wait-for-bootstrap:
    content: |
      #!/bin/bash

      echo "Waiting for Gitea bootstrap to complete..."

      until curl -sf -u 'moby:moby1234' http://git.dockerlabs.xyz/api/v1/user > /dev/null 2>&1; do
        echo "Waiting for moby user to be ready..."
        sleep 2
      done
      echo "moby user is ready."

      until curl -s -u 'moby:moby1234' http://git.dockerlabs.xyz/api/v1/user/keys | grep -q '"moby-key"'; do
        echo "Waiting for SSH key to be registered..."
        sleep 2
      done
      echo "SSH key is registered. Bootstrap complete."

  # Creates all CI secrets in the Gitea repo. Can be skipped by setting SKIP_CI_SECRET_SETUP=true,
  # which is useful for labs that guide users through creating the secrets themselves.
  workspace-ci-secrets:
    content: |
      #!/bin/bash

      if [ "$$SKIP_CI_SECRET_SETUP" = "true" ]; then
        echo "SKIP_CI_SECRET_SETUP is set. Skipping CI secret creation."
        exit 0
      fi

      SECRETS_JSON=$(curl -s -u 'moby:moby1234' http://git.dockerlabs.xyz/api/v1/repos/moby/demo-app/actions/secrets)

      # Create Gitea registry secrets
      if echo "$$SECRETS_JSON" | grep -q '"DOCKER_USERNAME"'; then
        echo "Gitea registry secrets already exist. Skipping."
      else
        curl -s -u 'moby:moby1234' -X PUT http://git.dockerlabs.xyz/api/v1/repos/moby/demo-app/actions/secrets/DOCKER_USERNAME -H "Content-Type: application/json" -d '{"data":"moby"}'
        curl -s -u 'moby:moby1234' -X PUT http://git.dockerlabs.xyz/api/v1/repos/moby/demo-app/actions/secrets/DOCKER_PASSWORD -H "Content-Type: application/json" -d '{"data":"moby1234"}'
        curl -s -u 'moby:moby1234' -X PUT http://git.dockerlabs.xyz/api/v1/repos/moby/demo-app/actions/secrets/DOCKER_REGISTRY -H "Content-Type: application/json" -d '{"data":"registry.dockerlabs.xyz"}'
        echo "Gitea registry secrets created."
      fi

      # Create Docker Hub secrets
      if echo "$$SECRETS_JSON" | grep -q '"DOCKERHUB_USERNAME"'; then
        echo "Docker Hub secrets already exist. Skipping."
      else
        HUB_AUTH=$(jq '.auths."https://index.docker.io/v1/".auth' -r ~/.docker/config.json | base64 -d -)
        USERNAME=$(echo $$HUB_AUTH | cut -d: -f1)
        TOKEN=$(echo $$HUB_AUTH | cut -d: -f2)

        curl -s -u 'moby:moby1234' -X PUT http://git.dockerlabs.xyz/api/v1/repos/moby/demo-app/actions/secrets/DOCKERHUB_USERNAME -H "Content-Type: application/json" -d '{"data":"'"$$USERNAME"'"}'
        curl -s -u 'moby:moby1234' -X PUT http://git.dockerlabs.xyz/api/v1/repos/moby/demo-app/actions/secrets/DOCKERHUB_PASSWORD -H "Content-Type: application/json" -d '{"data":"'"$$TOKEN"'"}'
        echo "Docker Hub secrets created."

        unset HUB_AUTH
        unset USERNAME
        unset TOKEN
      fi

      # Create KUBECONFIG secret
      if echo "$$SECRETS_JSON" | grep -q '"KUBECONFIG"'; then
        echo "KUBECONFIG secret already exists. Skipping."
      else
        KUBE_CONFIG=$(cat /kubeconfig/kubeconfig.yaml | sed 's/127.0.0.1/k8s.dockerlabs.xyz/g')
        curl -s -u 'moby:moby1234' -X PUT http://git.dockerlabs.xyz/api/v1/repos/moby/demo-app/actions/secrets/KUBECONFIG -H "Content-Type: application/json" -d '{"data":"'"$(echo -n "$$KUBE_CONFIG")"'"}'
        echo "KUBECONFIG secret created."
      fi

  workspace-git-setup:
    content: |
      #!/bin/bash

      # Configure the SSH key for git access
      sudo mkdir -p /home/coder/.ssh
      sudo cp /gitea-outputs/id_rsa /home/coder/.ssh/id_rsa 
      sudo cp /gitea-outputs/id_rsa.pub /home/coder/.ssh/id_rsa.pub
      sudo chown -R coder:coder /home/coder/.ssh

      # Setup git config
      ssh-keyscan -H git.dockerlabs.xyz >> /home/coder/.ssh/known_hosts
      git config --global init.defaultBranch main
      git config --global user.name 'Moby'
      git config --global user.email 'moby@local'
      
      # Commit the initial Labspace project to the git repo
      cd /home/coder/project

      # Ensure .gitignore exists
      touch .gitignore

      # Function to safely append to gitignore
      add_to_gitignore() {
        local entry="$$1"

        # Skip if entry already exists
        if grep -qxF "$$entry" .gitignore 2>/dev/null; then
          return
        fi

        # Ensure file ends with newline before appending (sed adds it if missing)
        sed -i -e '$$a\' .gitignore 2>/dev/null || true

        # Now append the entry
        echo "$$entry" >> .gitignore
      }

      rm -rf .git
      git init
      git remote add origin [email protected]:moby/demo-app.git
      git add -A
      git commit -m 'Initial commit'
      git push -u origin main

  gitea-bootstrap:
    content: |
      #!/bin/bash

      echo "Running"
      while ! curl -sL http://localhost:3000/user/login > /dev/null; do
        sleep 2
      done
      echo "Gitea is up - proceeding with bootstrap"

      # Create the 'moby' user if it doesn't exist
      users=$(su-exec git /usr/local/bin/gitea admin user list)
      if echo "$$users" | grep -q '^moby '; then
          echo "User 'moby' already exists. Skipping account creation."
      else
          su-exec git /usr/local/bin/gitea admin user create --username moby --password 'moby1234' --email admin@local --admin --must-change-password=false

          while ! curl -sL -u 'moby:moby1234' http://localhost:3000/api/v1/user > /dev/null; do
            echo "Waiting for user to be fully ready to use..."
            sleep 2
          done          
      fi
      
      # Check if demo-app repo exists. Create it if it doesn't.
      repo_status=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/api/v1/repos/moby/demo-app)
      if [ "$$repo_status" = "404" ]; then
          echo "Repository 'moby/demo-app' not found. Creating it..."
          curl -s -H "Content-type: application/json" -u 'moby:moby1234' -d '{"name":"demo-app","private":false}' http://localhost:3000/api/v1/user/repos
          echo "Repository 'moby/demo-app' created."
      else
          echo "Repository 'moby/demo-app' already exists. Skipping creation."
      fi

      # Get and store registration token for the runner
      TOKEN_JSON=$(curl -s -u 'moby:moby1234' http://localhost:3000/api/v1/repos/moby/demo-app/actions/runners/registration-token)
      REGISTRATION_TOKEN=$(echo $$TOKEN_JSON | grep -o '"token":"[^"]*"' | cut -d'"' -f4)
      echo -n $$REGISTRATION_TOKEN > /gitea-outputs/runner_token.txt

      # Create SSH key for user 'moby' if it doesn't exist
      if [ ! -f /gitea-outputs/id_rsa ]; then
          ssh-keygen -t rsa -b 4096 -N "" -f /gitea-outputs/id_rsa
          echo "SSH key generated for user 'moby'."
      fi

      # Add the public key to the user's SSH keys via Gitea API if not already added
      keys=$(curl -s -u 'moby:moby1234' http://localhost:3000/api/v1/user/keys)
      if echo "$$keys" | grep -q "moby-key"; then
          echo "SSH key already added to user 'moby'. Skipping key addition."
      else
        pub_key=$(cat /gitea-outputs/id_rsa.pub)
        curl -s -u 'moby:moby1234' http://localhost:3000/api/v1/user/keys -H "Content-Type: application/json" -d "{\"title\":\"moby-key\",\"key\":\"$$pub_key\"}"
        echo "SSH key added to user 'moby'."
      fi

  # Configure k3s with credentials and TLS settings for the Gitea registry
  k3s-registry-config:
    content: |
      configs:
        "registry.dockerlabs.xyz":
          auth:
            username: moby
            password: moby1234
          tls:
            insecure_skip_verify: true

  workspace-kubeconfig-setup:
    content: |
      #!/bin/bash

      set -e

      # Setup the kubeconfig for the coder user
      mkdir -p ~/.kube
      cat /kubeconfig/kubeconfig.yaml | sed 's/127.0.0.1/k8s.dockerlabs.xyz/g' > ~/.kube/config

  create-zone-file:
    content: |
      #!/bin/sh

      set -e

      IP=$$(dig +short git.dockerlabs.xyz A | grep -E '^[0-9.]+$$' | head -n 1)

      mkdir -p /labspace-support/k8s-setup
      cat << EOF > /labspace-support/k8s-setup/zone-file-config-map.yaml
      apiVersion: v1
      kind: ConfigMap
      metadata:
        name: coredns-custom
        namespace: kube-system
      data:
        dockerlabs-xyz.server: |
          dockerlabs.xyz:53 {
            log
            errors
            file /etc/coredns/custom/dockerlabs.xyz
          }
        dockerlabs.xyz: |
          \$$ORIGIN dockerlabs.xyz.
          \$$TTL 3600

          @   IN  SOA ns1.dockerlabs.xyz. hostmaster.dockerlabs.xyz. (
                  2026020601 ; serial (YYYYMMDDnn)
                  3600       ; refresh
                  900        ; retry
                  1209600    ; expire
                  3600       ; minimum
          )

              IN  NS  ns1.dockerlabs.xyz.

          ; A records
          @   IN  A   $$IP
          *   IN  A   $$IP
      EOF

  workspace-configure-k3s-coredns:
    content: |
      #!/bin/bash

      kubectl apply -f /etc/labspace-support/k8s-setup/zone-file-config-map.yaml
      if kubectl -n kube-system get deployment coredns > /dev/null 2>&1; then
        kubectl -n kube-system rollout restart deployment/coredns
      fiservices:
  configurator:
    environment:
      PROJECT_CLONE_URL: https://github.com/ams0/labspace-flux-gitops
configs:
  project_source_tar:
    content: |
      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

services:
  configurator:
    configs:
      - source: project_source_tar
        target: /etc/labspace-support/content.tar.base64
    environment:
      PROJECT_TAR_PATH: /etc/labspace-support/content.tar.base64

Docker commands

docker compose -f oci://ams0/labspace-flux-gitops:latest up

Use the above command to pull and run the Compose file. Learn more.

Images used

Image

A minimal Docker image based on Alpine Linux with a complete package index and only 5 MB in size!


Pulls

1B+

Stars

11554

Last Updated

3 days

Image + 1 more

Traefik, The Cloud Native Edge Router


Pulls

1B+

Stars

3665

Last Updated

about 4 hours

Image


Pulls

50M+

Stars

70

Last Updated

7 days

Image

Gitea: Git with a cup of tea - A painless self-hosted Git service.


Pulls

100M+

Stars

1057

Last Updated

2 days

Image

Deprecated Gitea runner. Moved to gitea/runner


Pulls

5M+

Stars

34

Last Updated

5 months

Image


Pulls

10K+

Stars

0

Last Updated

3 months

Image


Pulls

10K+

Stars

0

Last Updated

3 months

Image


Pulls

10K+

Stars

0

Last Updated

3 months

Image


Pulls

10K+

Stars

0

Last Updated

3 months

Image


Pulls

10K+

Stars

0

Last Updated

3 months

Image


Pulls

10K+

Stars

0

Last Updated

8 months