feat: add LinuxKit image builds using core CLI

Add LinuxKit configurations for developer and server-php images:
- developer/linuxkit.yml: Full dev environment with Docker-in-LinuxKit
- server-php/linuxkit.yml: Nginx + PHP-FPM production server

Update CI workflow to build LinuxKit images using `core build --type linuxkit`
instead of raw linuxkit CLI commands for consistency across the ecosystem.

Builds produce qcow2 and ISO formats for both amd64 and arm64 architectures.
Release artifacts are uploaded to GitHub Releases on version tags.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Snider 2026-01-28 22:23:22 +00:00
parent dafabd714c
commit 91f8aac50f
5 changed files with 747 additions and 22 deletions

View file

@ -69,6 +69,90 @@ jobs:
cache-from: type=gha cache-from: type=gha
cache-to: type=gha,mode=max cache-to: type=gha,mode=max
# ============================================================
# Build LinuxKit Images
# ============================================================
linuxkit:
name: LinuxKit (${{ matrix.image }}-${{ matrix.arch }})
runs-on: ubuntu-latest
needs: docker # Needs Docker images to be built first
strategy:
matrix:
image: [developer, server-php]
arch: [amd64, arm64]
format: [qcow2-bios, iso-bios]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Core CLI
run: |
# Download latest core binary
curl -fsSL "https://github.com/host-uk/core/releases/latest/download/core-linux-amd64.tar.gz" -o core.tar.gz
tar -xzf core.tar.gz
sudo mv core /usr/local/bin/core
chmod +x /usr/local/bin/core
core --version
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Login to GHCR
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build LinuxKit Image
run: |
mkdir -p dist
core build --type linuxkit \
--config ./${{ matrix.image }}/linuxkit.yml \
--format ${{ matrix.format }} \
--arch ${{ matrix.arch }} \
-o ./dist/${{ matrix.image == 'developer' && 'core-dev' || matrix.image }}-${{ matrix.arch }}
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.image == 'developer' && 'core-dev' || matrix.image }}-${{ matrix.arch }}-${{ matrix.format }}
path: ./dist/*
# ============================================================
# Release LinuxKit Images
# ============================================================
release-linuxkit:
name: Release LinuxKit Images
runs-on: ubuntu-latest
needs: linuxkit
if: startsWith(github.ref, 'refs/tags/v')
permissions:
contents: write
steps:
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: ./dist
merge-multiple: true
- name: Generate checksums
run: |
cd dist
sha256sum * > checksums.txt
- name: Upload to Release
uses: softprops/action-gh-release@v1
with:
files: |
dist/*
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# ============================================================ # ============================================================
# Build TIM Bundles (when core build --type tim is ready) # Build TIM Bundles (when core build --type tim is ready)
# ============================================================ # ============================================================

View file

@ -1,15 +1,16 @@
# core-images # core-images
Container images for the host-uk ecosystem. Each image produces dual outputs: Container images for the host-uk ecosystem. Each image produces multiple outputs:
- **Docker image**`ghcr.io/host-uk/<name>` - **Docker image**`ghcr.io/host-uk/<name>`
- **TIM bundle**`<name>-<os>-<arch>.tim` - **LinuxKit image**`<name>-<arch>.qcow2` / `<name>-<arch>.iso`
- **TIM bundle**`<name>-<os>-<arch>.tim` (future)
## Images ## Images
| Image | Purpose | Docker | TIM | | Image | Purpose | Docker | LinuxKit |
|-------|---------|--------|-----| |-------|---------|--------|----------|
| `developer` | Full-fat dev environment (100+ tools) | `ghcr.io/host-uk/core-dev` | `core-dev.tim` | | `developer` | Full-fat dev environment (100+ tools) | `ghcr.io/host-uk/core-dev` | `core-dev-amd64.qcow2` |
| `server-php` | Alpine + Nginx + PHP-FPM | `ghcr.io/host-uk/server-php` | `server-php.tim` | | `server-php` | Alpine + Nginx + PHP-FPM | `ghcr.io/host-uk/server-php` | `server-php-amd64.qcow2` |
## Usage ## Usage
@ -23,20 +24,50 @@ docker run -it ghcr.io/host-uk/core-dev
docker run -p 80:80 ghcr.io/host-uk/server-php docker run -p 80:80 ghcr.io/host-uk/server-php
``` ```
### TIM (Docker-free via Core) ### LinuxKit (via Core CLI)
```bash
# Build LinuxKit image
core build --type linuxkit --config developer/linuxkit.yml
# Run LinuxKit image
core run core-dev-amd64.qcow2
# Run with custom resources
core run core-dev-amd64.qcow2 --memory 4096 --cpus 4
```
### TIM (future, Docker-free)
```bash ```bash
# Install dev environment # Install dev environment
core dev install core dev install
# Or run directly # Run directly
core run core-dev.tim core run core-dev.tim
# Run PHP server # Run PHP server
core run server-php.tim -p 80:80 core run server-php.tim -p 80:80
``` ```
## Building ## Building Locally
### Using Core CLI (recommended)
```bash
# Install core CLI
go install github.com/host-uk/core/cmd/core@latest
# Build LinuxKit images
core build --type linuxkit --config developer/linuxkit.yml --format qcow2-bios
core build --type linuxkit --config server-php/linuxkit.yml --format qcow2-bios
# Build Docker images
core build --type docker --config developer/Dockerfile
core build --type docker --config server-php/Dockerfile
```
### Using Task
```bash ```bash
# Requires: task (taskfile.dev) # Requires: task (taskfile.dev)
@ -47,30 +78,36 @@ task build
# Build specific image # Build specific image
task build:developer task build:developer
task build:server-php task build:server-php
# Build TIM only
task build:developer:tim
# Build Docker only
task build:developer:docker
``` ```
## Structure ## Structure
``` ```
core-images/ core-images/
├── developer/ # core-dev.tim - Full dev environment ├── developer/ # core-dev - Full dev environment
│ ├── Dockerfile # Docker build
│ ├── linuxkit.yml # LinuxKit build
│ ├── Borgfile # TIM build (future)
│ └── config/
├── server-php/ # server-php - Nginx + PHP-FPM
│ ├── Dockerfile │ ├── Dockerfile
│ ├── linuxkit.yml
│ ├── Borgfile │ ├── Borgfile
│ └── config/ │ └── config/
├── server-php/ # server-php.tim - Nginx + PHP-FPM └── linuxkit/ # LinuxKit documentation
│ ├── Dockerfile └── README.md
│ ├── Borgfile
│ └── config/
└── base/ # Shared base configurations
└── alpine/
``` ```
## CI/CD
The GitHub Actions workflow builds:
1. **Docker images** - Multi-arch (amd64, arm64) pushed to GHCR
2. **LinuxKit images** - qcow2 and ISO formats for both architectures
3. **Release artifacts** - LinuxKit images uploaded to GitHub Releases on tags
All builds use the `core` CLI for consistency.
## Sources ## Sources
Consolidated from: Consolidated from:

230
developer/linuxkit.yml Normal file
View file

@ -0,0 +1,230 @@
# ============================================================
# LinuxKit Configuration - Developer Environment
#
# A complete developer environment mirroring the Docker
# developer image, built as a bootable VM.
#
# Build: linuxkit build -format qcow2-bios developer/linuxkit.yml
# Run: linuxkit run qemu developer
# ============================================================
kernel:
image: linuxkit/kernel:6.6.13
cmdline: "console=ttyS0 console=tty0"
init:
- linuxkit/init:v1.2.0
- linuxkit/runc:v1.1.12
- linuxkit/containerd:v1.7.13
- linuxkit/ca-certificates:v1.0.0
onboot:
# System initialization
- name: sysctl
image: linuxkit/sysctl:v1.0.0
- name: sysfs
image: linuxkit/sysfs:v1.0.0
- name: modprobe
image: linuxkit/modprobe:v1.0.0
command: ["modprobe", "-a", "overlay", "nf_conntrack", "br_netfilter"]
# Format and mount persistent workspace volume
- name: format
image: linuxkit/format:v1.0.0
- name: mount
image: linuxkit/mount:v1.0.0
command: ["/usr/bin/mountie", "/var/lib/docker", "/workspace"]
onshutdown:
- name: shutdown
image: linuxkit/shutdown:v1.0.0
services:
# ============================================================
# Core Services
# ============================================================
- name: getty
image: linuxkit/getty:v1.0.0
env:
- INSECURE=true
binds:
- /etc/profile.d:/etc/profile.d
capabilities:
- CAP_SYS_ADMIN
- CAP_SYS_TTY_CONFIG
- name: rngd
image: linuxkit/rngd:v1.0.0
- name: dhcpcd
image: linuxkit/dhcpcd:v1.0.0
- name: ntpd
image: linuxkit/openntpd:v1.0.0
# ============================================================
# SSH Access
# ============================================================
- name: sshd
image: linuxkit/sshd:v1.0.0
binds:
- /etc/ssh/authorized_keys:/root/.ssh/authorized_keys
- /workspace:/workspace
capabilities:
- CAP_NET_BIND_SERVICE
- CAP_SYS_CHROOT
- CAP_SETUID
- CAP_SETGID
# ============================================================
# Docker-in-LinuxKit (DinL)
# ============================================================
- name: dockerd
image: docker:26.1-dind
capabilities:
- all
net: host
pid: host
mounts:
- type: cgroup
options: ["rw", "nosuid", "noexec", "nodev", "relatime"]
binds:
- /etc/resolv.conf:/etc/resolv.conf
- /var/lib/docker:/var/lib/docker
- /var/run:/var/run
- /workspace:/workspace
runtime:
mkdir:
- /var/lib/docker
# ============================================================
# Developer Environment Container
# ============================================================
- name: developer
image: ghcr.io/host-uk/core-dev:latest
capabilities:
- CAP_NET_ADMIN
- CAP_SYS_ADMIN
- CAP_SETUID
- CAP_SETGID
net: host
binds:
- /workspace:/workspace
- /var/run/docker.sock:/var/run/docker.sock
- /etc/profile.d:/etc/profile.d:ro
env:
- TERM=xterm-256color
- LANG=C.UTF-8
- LC_ALL=C.UTF-8
- SHELL=/bin/zsh
- GOPATH=/root/go
- PATH=/root/go/bin:/root/.local/bin:/root/.composer/vendor/bin:/usr/local/bin:/usr/bin:/bin
runtime:
mkdir:
- /workspace
# ============================================================
# Static Files
# ============================================================
files:
# SSH authorized keys (placeholder - mount your own)
- path: /etc/ssh/authorized_keys
contents: |
# Add your SSH public keys here
# ssh-ed25519 AAAA... user@host
mode: "0600"
# Profile for shell environment
- path: /etc/profile.d/00-developer.sh
contents: |
#!/bin/sh
export TERM=xterm-256color
export LANG=C.UTF-8
export LC_ALL=C.UTF-8
export SHELL=/bin/zsh
export EDITOR=vim
export GOPATH=/root/go
export PATH="/root/go/bin:/root/.local/bin:/root/.composer/vendor/bin:/usr/local/bin:$PATH"
cd /workspace 2>/dev/null || true
mode: "0644"
# Shell aliases from developer config
- path: /etc/profile.d/aliases.sh
contents: |
# Core-dev shell aliases
# Navigation
alias ..='cd ..'
alias ...='cd ../..'
alias ll='eza -la --icons --git'
alias la='eza -la --icons'
alias lt='eza --tree --level=2 --icons'
# Git
alias g='git'
alias gs='git status'
alias gd='git diff'
alias gc='git commit'
alias gp='git push'
alias gl='git log --oneline -20'
alias gco='git checkout'
alias gb='git branch'
alias lg='lazygit'
# Docker
alias d='docker'
alias dc='docker compose'
alias dps='docker ps'
alias di='docker images'
alias dex='docker exec -it'
# Kubernetes
alias k='kubectl'
alias kgp='kubectl get pods'
alias kgs='kubectl get svc'
alias kgd='kubectl get deployments'
# PHP/Laravel
alias art='php artisan'
alias sail='./vendor/bin/sail'
alias pest='./vendor/bin/pest'
alias pint='./vendor/bin/pint'
# Core
alias c='core'
alias cdev='core dev'
alias cbuild='core build'
alias crun='core run'
# Misc
alias cat='bat'
alias find='fd'
alias grep='rg'
mode: "0644"
# Motd
- path: /etc/motd
contents: |
╔══════════════════════════════════════════════════════════════╗
║ Host UK Core Developer Environment ║
║ ║
║ Tools: Node, Go, PHP, Python, Docker ║
║ Workspace: /workspace (persistent) ║
║ ║
║ Run 'claude' to start AI-assisted development ║
╚══════════════════════════════════════════════════════════════╝
mode: "0644"
# ============================================================
# Trust Configuration
# ============================================================
trust:
org:
- linuxkit
- library
- docker

102
linuxkit/README.md Normal file
View file

@ -0,0 +1,102 @@
# LinuxKit Images
This directory contains documentation for LinuxKit image builds.
## Building with Core CLI
The recommended way to build LinuxKit images is using the `core` CLI:
```bash
# Install core CLI
go install github.com/host-uk/core/cmd/core@latest
# Build developer image (QCOW2 for QEMU/KVM)
core build --type linuxkit --config developer/linuxkit.yml --format qcow2-bios
# Build server-php image (QCOW2 for QEMU/KVM)
core build --type linuxkit --config server-php/linuxkit.yml --format qcow2-bios
# Build ISO for bare metal / other hypervisors
core build --type linuxkit --config developer/linuxkit.yml --format iso-bios
core build --type linuxkit --config server-php/linuxkit.yml --format iso-bios
# Build for specific architecture
core build --type linuxkit --config developer/linuxkit.yml --arch arm64
```
## Running Images
```bash
# Run with core CLI
core run core-dev-amd64.qcow2
# Run with custom resources
core run core-dev-amd64.qcow2 --memory 4096 --cpus 4
# Run in detached mode
core run -d core-dev-amd64.qcow2
```
## Output Formats
| Format | Use Case |
|--------|----------|
| `qcow2-bios` | QEMU/KVM (default) |
| `iso-bios` | Bare metal, VMware, VirtualBox |
| `raw-bios` | Direct disk write |
| `vhd` | Hyper-V |
| `vmdk` | VMware |
| `aws` | AWS EC2 AMI |
| `gcp` | Google Cloud |
| `azure` | Microsoft Azure |
## Image Architecture
### Developer Image
The developer LinuxKit image provides:
- Full developer toolchain (Node, Go, PHP, Python)
- Docker-in-LinuxKit for container workflows
- SSH access for remote development
- Persistent `/workspace` volume
- Pre-configured shell with aliases and starship prompt
### Server PHP Image
The server PHP LinuxKit image provides:
- Minimal Alpine base
- Nginx + PHP-FPM stack
- SSH access for management
- Health check endpoint at `/health`
- Persistent `/var/www/html` volume
## Customization
### Adding SSH Keys
Edit the `authorized_keys` file in the linuxkit.yml:
```yaml
files:
- path: /etc/ssh/authorized_keys
contents: |
ssh-ed25519 AAAA... your-key@host
mode: "0600"
```
### Environment Variables
Add environment variables to the service definition:
```yaml
services:
- name: developer
env:
- MY_VAR=value
```
### Custom Packages
For additional packages, create a custom container image that inherits from the base and add it to the LinuxKit config.

272
server-php/linuxkit.yml Normal file
View file

@ -0,0 +1,272 @@
# ============================================================
# LinuxKit Configuration - PHP Server
#
# A minimal production PHP server with Nginx + PHP-FPM,
# built as a bootable VM.
#
# Build: linuxkit build -format qcow2-bios server-php/linuxkit.yml
# Run: linuxkit run qemu server-php
# ============================================================
kernel:
image: linuxkit/kernel:6.6.13
cmdline: "console=ttyS0 console=tty0"
init:
- linuxkit/init:v1.2.0
- linuxkit/runc:v1.1.12
- linuxkit/containerd:v1.7.13
- linuxkit/ca-certificates:v1.0.0
onboot:
# System initialization
- name: sysctl
image: linuxkit/sysctl:v1.0.0
- name: sysfs
image: linuxkit/sysfs:v1.0.0
# Format and mount persistent data volume
- name: format
image: linuxkit/format:v1.0.0
- name: mount
image: linuxkit/mount:v1.0.0
command: ["/usr/bin/mountie", "/var/www/html"]
onshutdown:
- name: shutdown
image: linuxkit/shutdown:v1.0.0
services:
# ============================================================
# Core Services
# ============================================================
- name: rngd
image: linuxkit/rngd:v1.0.0
- name: dhcpcd
image: linuxkit/dhcpcd:v1.0.0
- name: ntpd
image: linuxkit/openntpd:v1.0.0
# ============================================================
# SSH Access (for management)
# ============================================================
- name: sshd
image: linuxkit/sshd:v1.0.0
binds:
- /etc/ssh/authorized_keys:/root/.ssh/authorized_keys
capabilities:
- CAP_NET_BIND_SERVICE
- CAP_SYS_CHROOT
- CAP_SETUID
- CAP_SETGID
# ============================================================
# PHP Server Container
# ============================================================
- name: server-php
image: ghcr.io/host-uk/server-php:latest
capabilities:
- CAP_NET_BIND_SERVICE
- CAP_CHOWN
- CAP_SETUID
- CAP_SETGID
net: host
binds:
- /var/www/html:/var/www/html
- /etc/php-server:/etc/php-server:ro
env:
- APP_ENV=production
- PHP_VERSION=84
runtime:
mkdir:
- /var/www/html
# ============================================================
# Health Check Service
# ============================================================
- name: healthcheck
image: linuxkit/healthcheck:v1.0.0
binds:
- /run:/run
capabilities:
- CAP_NET_RAW
command:
- /healthcheck
- --endpoint=http://127.0.0.1/health
- --interval=30s
- --timeout=10s
# ============================================================
# Static Files
# ============================================================
files:
# SSH authorized keys (placeholder - mount your own)
- path: /etc/ssh/authorized_keys
contents: |
# Add your SSH public keys here
# ssh-ed25519 AAAA... user@host
mode: "0600"
# PHP-FPM configuration
- path: /etc/php-server/php-fpm.conf
contents: |
[global]
pid = /run/php-fpm.pid
error_log = /proc/self/fd/2
daemonize = no
[www]
user = nobody
group = nobody
listen = /run/php-fpm.sock
listen.owner = nobody
listen.group = nobody
listen.mode = 0660
pm = dynamic
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 35
pm.max_requests = 500
clear_env = no
catch_workers_output = yes
decorate_workers_output = no
php_admin_value[error_log] = /proc/self/fd/2
php_admin_flag[log_errors] = on
mode: "0644"
# Nginx configuration
- path: /etc/php-server/nginx.conf
contents: |
worker_processes auto;
error_log /dev/stderr warn;
pid /run/nginx.pid;
events {
worker_connections 1024;
multi_accept on;
use epoll;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /dev/stdout main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# Gzip compression
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript
application/rss+xml application/atom+xml image/svg+xml;
server {
listen 80;
listen [::]:80;
server_name _;
root /var/www/html/public;
index index.php index.html;
# Health check endpoint
location /health {
access_log off;
return 200 "OK\n";
add_header Content-Type text/plain;
}
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/run/php-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_hide_header X-Powered-By;
}
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Deny hidden files
location ~ /\. {
deny all;
}
}
}
mode: "0644"
# Supervisor configuration (used inside the container)
- path: /etc/php-server/supervisord.conf
contents: |
[supervisord]
nodaemon=true
user=root
logfile=/dev/null
logfile_maxbytes=0
pidfile=/run/supervisord.pid
[program:php-fpm]
command=/usr/sbin/php-fpm84 -F -y /etc/php-server/php-fpm.conf
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
autorestart=true
startretries=5
[program:nginx]
command=/usr/sbin/nginx -g 'daemon off;' -c /etc/php-server/nginx.conf
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
autorestart=true
startretries=5
depends_on=php-fpm
mode: "0644"
# Motd
- path: /etc/motd
contents: |
╔══════════════════════════════════════════════════════════════╗
║ Host UK Core PHP Server ║
║ ║
║ Stack: Alpine + Nginx + PHP-FPM ║
║ Webroot: /var/www/html ║
║ ║
║ Health: http://localhost/health ║
╚══════════════════════════════════════════════════════════════╝
mode: "0644"
# ============================================================
# Trust Configuration
# ============================================================
trust:
org:
- linuxkit
- library