In this article I will share the steps to create custom ISO using original RHEL/CentOS image. The original DVD of RHEL/CentOS is quiet huge (around 9Gb for RHEL 8.3), in production environment we normally create our own custom ISO with reduced number of packages and deliver the same to our clients and customers.
The steps from this article are verified for CentOS 8 but the same will also work for RHEL 8. But if you want to create custom ISO for RHEL/CentOS 7 then I would recommend to follow How to create customized bootable ISO image in RHEL/CentOS 7
Step 1: Access the original ISO Image
First of all you would need access to the original ISO DVD image which we will further customise and generate a small version of the same image with lesser rpm count. I will be using CentOS 8.2 for the demonstration of this article. I have already added the image on my Oracle VirtualBox VM and have mounted it on /mnt
:
~]# mount /dev/sr0 /mnt
mount: /mnt: WARNING: device write-protected, mounted read-only.
Step 2: Generate directory structure
Next we need to have same directory structure as used in the original DVD. But since we don't want to copy all the rpms from the original DVD, so we will exclude the folders which contains packages.
BaseOS/Packages
and AppStream/Packages
. So make sure you update your configuration based on your environment.I will create a base directory /data/custom_iso
under which I will copy the ISO content:
~]# mkdir /data/custom_iso
Since we are using CentOS 8, we will copy everything except BaseOS
and AppStream
directory from the original DVD.
~]# rsync -av --progress /mnt/ /data/custom_iso/ --exclude BaseOS --exclude AppStream
This will copy all the files and folders from ISO mounted on /mnt
into /data/custom
. But there are also some hidden files in the root folder of the ISO which also must be copied or else your ISO will never work.
~]# cp -vf /mnt/.??* /data/custom_iso/
'/mnt/.discinfo' -> '/data/custom_iso/.discinfo'
'/mnt/.treeinfo' -> '/data/custom_iso/.treeinfo'
These are the final content under my custom_iso
directory at this stage:
~]# ls -al total 48 drwxr-xr-x. 7 root root 4096 Feb 25 01:32 . drwxr-xr-x. 4 root root 4096 Feb 25 00:09 .. -r--r--r--. 1 root root 44 Feb 25 00:36 .discinfo dr-xr-xr-x. 3 root root 4096 Jun 9 2020 EFI dr-xr-xr-x. 3 root root 4096 Jun 9 2020 images dr-xr-xr-x. 2 root root 4096 Feb 24 23:18 isolinux -r--r--r--. 1 root root 87 Jun 9 2020 media.repo -r--r--r--. 1 root root 664 Jun 9 2020 TRANS.TBL -r--r--r--. 1 root root 1520 Feb 25 00:36 .treeinfo
Step 3: Create package list
This is the most tricky part unless you are planning to use all the rpms from the original DVD (in which case you can copy the entire directory into /data/custom_iso
in the previous step). I know of two methods which can be used to achieve this, you can add any more which you felt was more easier using the comment section.
Method 1: Getting list of packages using DNF/YUM
In this method we will get the list of packages for an individual group using YUM or DNF and then copy those packages into BaseOS/AppStream directory respectively. First you must decide the list of group(s) which you plan to install as part of your custom ISO. To get the list of available groups you can use:
]# dnf grouplist Last metadata expiration check: 0:00:06 ago on Thu 25 Feb 2021 11:15:49 AM IST. Available Environment Groups: Server Workstation KDE Plasma Workspaces Virtualization Host Custom Operating System Installed Environment Groups: Server with GUI Minimal Install Installed Groups: Container Management Headless Management GNOME Desktop Environment Hardware Support Available Groups: .NET Core Development RPM Development Tools Development Tools Graphical Administration Tools Legacy UNIX Compatibility Network Servers Scientific Support Security Tools Smart Card Support System Tools Fedora Packager Xfce Games and Entertainment Graphical Internet KDE (K Desktop Environment) Sound and Video
For example I want to have an ISO with rpms required to install "Minimal Environment
" group. So to get the list of rpms part of this group we can use:
~]# dnf groupinfo "Minimal Install" Last metadata expiration check: 0:02:31 ago on Thu 25 Feb 2021 11:15:49 AM IST. Environment Group: Minimal Install Description: Basic functionality. Mandatory Groups: Core Optional Groups: Guest Agents Standard
So looks like this group is composed of different sub groups, so we need to query these individual groups to get the list of packages:
~]# dnf groupinfo "Core" "Guest Agents" "Standard"
This will give you a long output with the list of following types of packages for individual groups:
- Optional Packages
- Default Packages
- Mandatory Packages
You can write a small script to get the list of packages and then copy those packages from the original DVD into respective folder of custom_iso
. As some of these packages will be part of BaseOS
repo while the others would be part of AppStream
repo so you must maintain the hierarchy.
Method 2: Bring up a new instance using respective ISO
I personally prefer this method as the success rate is much better to get a working list of packages. In this method we will install CentOS 8 on one of my VMs and then select the respective groups which you plan to include in your custom ISO.
Under the installation option stage:
You can select your preferred group:
I would not recommend selecting any extra add-on packages at this stage unless you know what you are doing. You can always manually install the packages later once the node is UP based on your requirement.
dnf groupinstall <group_name>
" once this node is installed. This would install the packages and all the dependencies from the provided group_name
. But it is important that you use offline repository for performing this step as if you use online repositories then you may not have access to those rpms in the DVD. In such case you can just download the respective rpm and dependent rpms from online repositories.Once this VM is installed with CentOS 8, you can go ahead and install any additional packages or groups based on your requirement:
# dnf install <pkg> # dnf groupinstall <group>
Next query the list of rpms from this newly created VM and store the output in a file and copy this file to your original node where you were creating the custom ISO:
~]# rpm -qa --queryformat %{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}'\n' > /tmp/rpms.txt
This will store the rpm along with the version, release and architecture format into /tmp/rpms.txt
. So without much hassle we have our list of rpms with all the dependencies.
Step 4: Copy rpms based on package list
Now that we have our package list with the list of packages we want in our custom ISO, we have to copy these rpms from the original DVD to our custom ISO. It would have been very easy in RHEL/CentOS 7 where we had a single location for all the packages but with RHEL/CentOS 8 now we have BaseOS
and AppStream
directory for the packages so we must make sure that rpms are copied to their respective directory.
At Step 2 we had skipped the copying of BaseOS
and AppStream
directory so let us manually create these directories to store the packages:
~]# mkdir -p /data/custom_iso/BaseOS/Packages /data/custom_iso/AppStream/Packages
Now to copy the rpms I have written a small script, this script assumes that the original DVD is mounted on /mnt
which contains all the rpms and we are storing the output in /data/custom_iso
~]# cat /tmp/copy_rpms_to_myiso.sh #!/bin/bash RPM_FILE="/tmp/rpms.txt" for pkg in `cat $RPM_FILE`; do RPM=`find /mnt/ -name $pkg*.rpm` if [[ $RPM =~ .*BaseOS.* ]];then cp -ap $RPM /data/custom_iso/BaseOS/Packages/ elif [[ $RPM =~ .*AppStream.* ]];then cp -ap $RPM /data/custom_iso/AppStream/Packages/ else echo "$pkg not found" fi done
This script will check if the individual rpm is from BaseOS
or AppStream
repository and then will copy the rpm into respective directory. If the script fails to find the rpm in both the repository then it will print the package name which was not found.
Let us execute this script:
~]# /tmp/copy_rpms_to_myiso.sh
If everything is correct then this step should copy all the packages into it's respective folder.
Step 5: Create repodata for Custom ISO
Now that we have all the required rpms in place under respective repo directory, next we must create repodata
for this individual repositories. To achieve this we will need createrepo
tool which you can install using yum or dnf:
~]# dnf install createrepo
Next we would need Comps.xml
file from the original DVD for both the repositories. This comps.xml contains the list of groups and the mapping packages for individual group, this file is normally referred by the installation medium to identify the available groups and add-on packages.
So copy the respective comps.xml and place it in your custom ISO
[root@centos8 custom_iso]# cp /mnt/BaseOS/repodata/*comps*.xml BaseOS/comps_base.xml [root@centos8 custom_iso]# cp /mnt/AppStream/repodata/*comps*.xml AppStream/comps_app.xml
Next create repo using these group xml file for both BaseOS
and AppStream
:
[root@centos8 custom_iso]# createrepo -g comps_app.xml AppStream/ Directory walk started Directory walk done - 29 packages Temporary output repo path: AppStream/.repodata/ Preparing sqlite DBs Pool started (with 5 workers) Pool finished [root@centos8 custom_iso]# createrepo -g comps_base.xml BaseOS/ Directory walk started Directory walk done - 360 packages Temporary output repo path: BaseOS/.repodata/ Preparing sqlite DBs Pool started (with 5 workers) Pool finished
This step will create repodata
directory with the required database files under /data/custom_iso/BaseOS
and /data/custom_iso/AppStream
directory:
[root@centos8 custom_iso]# ls -l BaseOS/repodata/ AppStream/repodata/ AppStream/repodata/: total 808 -rw-r--r--. 1 root root 10856 Feb 25 02:02 231deccd5b8e4dc32047ccbe4f6b0b0683a6b0253cc3e3dc89093b6ae9e522da-other.xml.gz -rw-r--r--. 1 root root 15238 Feb 25 02:02 39e06ef307b975417fcfd5ec9bc23c84495407c136e430837eda3508140e7964-other.sqlite.bz2 -rw-r--r--. 1 root root 19034 Feb 25 02:02 4d1f47a7d096fdbe9bdfccb84ea596be9a09ec6d51cf0b8b67f5c3663775261d-filelists.sqlite.bz2 -rw-r--r--. 1 root root 556980 Feb 25 02:02 636d97191c77227309938cd1eb97fce7139c880dd52a57d950d1094bd170ddfa-comps_app.xml -rw-r--r--. 1 root root 18477 Feb 25 02:02 8b15e126c078293ffb47e38f32c73ebf5c19878f920b696fac81e37f974aafaf-primary.sqlite.bz2 -rw-r--r--. 1 root root 9714 Feb 25 02:02 9e9d9bfebabb219028fc2fb1b479b1868dc30aecfd07ce7da75935183a9ba644-primary.xml.gz -rw-r--r--. 1 root root 116050 Feb 25 02:02 e9524923e26fe119880aa4929454fcff9ed8877f7e5ace47928805a5cbeeb920-comps_app.xml.gz -rw-r--r--. 1 root root 10460 Feb 25 02:02 fbc30afdbd054a4868d5d3fa10f91d647e95d833ebe0452a8fb15167708a643f-filelists.xml.gz -rw-r--r--. 1 root root 4316 Feb 25 02:02 repomd.xml BaseOS/repodata/: total 2680 -rw-r--r--. 1 root root 153383 Feb 25 01:38 38630dc278c0790d7a0ca019208c223dc9cdd384f265272e6b3489effa7ae63c-other.xml.gz -rw-r--r--. 1 root root 347027 Feb 25 01:38 586a8e96ad3b73414e5c6ae94a5e4f128a087fcb0ca9837674573a8d6cac4a9c-comps_base.xml -rw-r--r--. 1 root root 373357 Feb 25 01:38 77b091786d530232d4465543457fe73307a61d359dbd3704abb9656b38d3d339-filelists.sqlite.bz2 -rw-r--r--. 1 root root 426066 Feb 25 01:38 7c83828e61d655fa0908d64f69d9f0b88c72972efb880c5e0285d3f9dd7eb3ec-primary.xml.gz -rw-r--r--. 1 root root 185860 Feb 25 01:38 ae1b59594f9141e982cf97cab724c4f2c454814740edde657791bf1f2f75a7d2-other.sqlite.bz2 -rw-r--r--. 1 root root 78056 Feb 25 01:38 c1437f9c1bd0a3940a7ef97288dbfc6247fa71f9fded4689cc3d16213ad78897-comps_base.xml.gz -rw-r--r--. 1 root root 298383 Feb 25 01:38 d6b6730e96a51b83763e5e75109e603a5023b7237496fb3ee47567f65d700cfb-filelists.xml.gz -rw-r--r--. 1 root root 857212 Feb 25 01:38 e1c8b7e4a5d8da239510f494f92cb156db3722dcf4b1bf7b24553ab3c3974cf7-primary.sqlite.bz2 -rw-r--r--. 1 root root 3873 Feb 25 01:38 repomd.xml
Next we need to create modules file for AppStream
repository because if you check the repodata
in the original DVD, it will have one additional file:
[root@centos8 custom_iso]# ls -l /mnt/AppStream/repodata/*modules*
-r--r--r--. 1 root root 46270 Aug 16 2019 /mnt/AppStream/repodata/f8cbb89b908e10e2e01b6b5555a116ba365f96cd4b5aa8414aa5465f82a021fd-modules.yaml.gz
So we must add the modular metadata (modulemd
) using modifyrepo_c
inside the same repodata of our custom ISO. To achieve this we must copy the original modules.xml
from the source ISO:
[root@centos8 custom_iso]# cp /mnt/AppStream/repodata/*modules.yaml* AppStream/
Unzip the content of this file as it is in .gz
format:
[root@centos8 custom_iso]# cd AppStream
[root@centos8 AppStream]# gunzip f8cbb89b908e10e2e01b6b5555a116ba365f96cd4b5aa8414aa5465f82a021fd-modules.yaml.gz
[root@centos8 AppStream]# ls -l *modules*.yaml
total 876
-r--r--r--. 1 root root 281029 Feb 25 02:01 f8cbb89b908e10e2e01b6b5555a116ba365f96cd4b5aa8414aa5465f82a021fd-modules.yaml
We will rename the file for easier usage:
[root@centos8 AppStream]# cp f8cbb89b908e10e2e01b6b5555a116ba365f96cd4b5aa8414aa5465f82a021fd-modules.yaml modules.yaml
Next to add the modular metadata (modulemd
), run:
[root@centos8 custom_iso]# modifyrepo_c --mdtype=modules AppStream/modules.yaml AppStream/repodata/
This will create the respective modules.yaml
file inside AppStream/repodata
directory:
[root@centos8 custom_iso]# ls -l AppStream/repodata/
total 808
-rw-r--r--. 1 root root 10856 Feb 25 02:02 231deccd5b8e4dc32047ccbe4f6b0b0683a6b0253cc3e3dc89093b6ae9e522da-other.xml.gz
-rw-r--r--. 1 root root 15238 Feb 25 02:02 39e06ef307b975417fcfd5ec9bc23c84495407c136e430837eda3508140e7964-other.sqlite.bz2
-rw-r--r--. 1 root root 19034 Feb 25 02:02 4d1f47a7d096fdbe9bdfccb84ea596be9a09ec6d51cf0b8b67f5c3663775261d-filelists.sqlite.bz2
-rw-r--r--. 1 root root 556980 Feb 25 02:02 636d97191c77227309938cd1eb97fce7139c880dd52a57d950d1094bd170ddfa-comps_app.xml
-rw-r--r--. 1 root root 18477 Feb 25 02:02 8b15e126c078293ffb47e38f32c73ebf5c19878f920b696fac81e37f974aafaf-primary.sqlite.bz2
-rw-r--r--. 1 root root 9714 Feb 25 02:02 9e9d9bfebabb219028fc2fb1b479b1868dc30aecfd07ce7da75935183a9ba644-primary.xml.gz
-rw-r--r--. 1 root root 116050 Feb 25 02:02 e9524923e26fe119880aa4929454fcff9ed8877f7e5ace47928805a5cbeeb920-comps_app.xml.gz
-rw-r--r--. 1 root root 46270 Feb 25 02:02 f8cbb89b908e10e2e01b6b5555a116ba365f96cd4b5aa8414aa5465f82a021fd-modules.yaml.gz
-rw-r--r--. 1 root root 10460 Feb 25 02:02 fbc30afdbd054a4868d5d3fa10f91d647e95d833ebe0452a8fb15167708a643f-filelists.xml.gz
-rw-r--r--. 1 root root 4316 Feb 25 02:02 repomd.xml
So finally our repository configuration is completed, so let's move on to the next step.
Step 6: Add custom third party rpms (Optional)
Now you may have a requirement to also add some custom third party rpms into your custom CPIO which are not available in the vanilla DVD by default so let me show you the steps to add those to your custom ISO. But if you do not have any such requirement then you can skip this section.
It is strictly not recommended to modify default repos i.e. AppStream and BaseOS with custom rpms so we need to create a new repository for our custom rpms. Navigate inside custom_iso directory which we created earlier
[root@centos8 ~]# cd /data/custom_iso
Create a new directory with your repo name which you want to use, we will call it ExtraPackages
:
[root@centos8 custom_iso]# mkdir ExtraPackages; cd ExtraPackages
Now copy all your rpms to this path. Once you are done execute createrepo
to create all repodata content inside ExtraPackages
. This will create the required repository data files for perform installation using this repo.
[root@centos8 ExtraPackages]# createrepo . [root@centos8 ExtraPackages]# createrepo . [root@centos8 ExtraPackages]# # ls -l total 112 -rw-r--r-- 1 root root 64492 Feb 11 2021 perl-WWW-Curl-4.17-21.el8.x86_64.rpm -rw-r--r-- 1 root root 41932 Feb 11 2021 perl-Net-IP-1.26-20.el8.noarch.rpm drwxr-xr-x 2 root root 4096 Feb 11 2021 repodata
repodata
directory is inside the ExtraPackages
directory to consider that as a repo.
Step 7: Create kickstart configuration file
I hope you are aware that in RHEL/CentOS we use kickstart to automate the installation procedure. I have already written a detailed article on kickstart configuration along with a sample kickstart file. Here you can use the kickstart template from the VM which you might have created at Step 3 (Method-2) under /root/anaconda-ks.cfg
I will not be able to explain all the individual section of the kickstart as that is out of scope for this article, I have just placed a sample kickstart from my working node where I verified these steps:
#platform=x86, AMD64, or Intel EM64T #version=RHEL8 repo --name="AppStream" --baseurl=file:///run/install/repo/AppStream # Add additional repo name where you have added custom rpm repo --name="ExtraPackages" --baseurl=file:///run/install/repo/ExtraPackages # Only use sda disk ignoredisk --only-use=sda # System bootloader configuration bootloader --append="rhgb novga console=ttyS0,115200 console=tty0 panic=1" --location=mbr --driveorder="sda" --boot-drive=sda # Clear the Master Boot Record zerombr # Partition clearing information clearpart --all # Reboot after installation reboot # Use text mode install text # Use CDROM cdrom # Keyboard layouts keyboard --vckeymap=us --xlayouts='' # System language lang en_US.UTF-8 # Installation logging level logging --level=info # Network information network --bootproto=dhcp --device=enp0s3 --noipv6 --activate --hostname server1.example.com # Root password rootpw --iscrypted $1$oXhMRzpse6FeGBc1uF2JmG2xTeSWPL9 # System authorization information authselect --enableshadow --passalgo=sha512 # SELinux configuration selinux --disabled firstboot --disable # Do not configure the X Window System skipx # System services services --disabled="kdump,rpcbind,sendmail,postfix,chronyd" # System timezone timezone Asia/Kolkata --isUtc --ntpservers=10.43.12.34 # Disk partitioning information autopart --type=lvm %packages @^minimal-environment smartmontools sysstat tmux tuned zip %end %addon com_redhat_kdump --disable --reserve-mb='auto' %end
You can also add %pre and %post section in the kickstart to add your custom scripts and configurations.
Place this kickstart file in the home folder of the custom ISO i.e. /data/custom_iso
in our example:
[root@centos8 custom_iso]# ls -l ks.cfg
-rw-------. 1 root root 1845 Feb 25 01:32 ks.cfg
Step 8: Create/Modify the GRUB2 Menu
Next we will modify the GRUB2 splash screen which gives us the option to perform the installation, you could also modify the splash screen of the GRUB2 menu. We will use the default configuration file with little modification to support our customization.
Locate isolinux.cfg
under isolinux
directory of the custom ISO and modify the highlighted section:
FROM:
label linux
menu label ^Install CentOS Linux 8.0.1905
kernel vmlinuz
append initrd=initrd.img inst.stage2=hd:LABEL=CentOS-8-BaseOS-x86_64 quiet
TO:
label linux
menu label ^Install CentOS Linux 8
kernel vmlinuz
append initrd=initrd.img inst.repo=cdrom ks=cdrom:/ks.cfg quiet
If you have placed you kickstart file at a different location then you can modify the above entry accordingly.
Step 9: Create custom ISO
So finally we are at the final stages of this article. You are just one step away from having your own custom ISO. We require mkisofs
tool to create our custom ISO which is part of genisoimage
in RHEL/CentOS 8 so to install this you can use yum/dnf:
~]# dnf install genisoimage
Next use the following command to generate your ISO (assuming you are under /data/custom_iso
):
[root@centos8 custom_iso]# mkisofs -o /tmp/new.iso -b isolinux/isolinux.bin -c isolinux/boot.cat --no-emul-boot --boot-load-size 4 --boot-info-table -J -R -V "CentOS-8-2-2004-x86_64-dvd" .
Here this command will generate /tmp/new.iso
with the LABEL CentOS-8-2-2004-x86_64-dvd
.
Step 10: Validating the ISO
Now that we have our custom ISO ready, it is time to create a new VM using this image. Once the VM boots using the custom image, select the option to "Install CentOS Linux 8" and press TAB which will show you the isolinux.cfg
content which we modified in Step 7
Hit Enter to start the installation:
So as you see the installation has started successfully.
Summary
In this tutorial we learned how to create custom ISO with minimzed package list in RHEL/CentOS 8. You may feel some parts of this article complicate, specially the step where we generated our repodata. In such case you can always choose to take the default repodata
from the original DVD for both BaseOS and AppStream and that should also work but the repodata database will contain details of rpms which are actually not present in the DVD. Although this will not impact the functioning of the ISO. You can further enhance the steps by adding %pre scripts to prompt of user password or network details which can then be parsed into the kickstart.
Any chance to create a tested minimal iso for rhel 9.2 with a link to download? My old box does not like a usb boot. Thank you!
Does not work with UEFI systems.
Some changes may be required, I don’t have the environment to test and share the steps. If there is any specific issue then I may be able to help you through it.
I got this sorted. The problem is that for UEFI, you need to modify the grub boot entries in grub.cfg. There’s a copy of this file in the top-level EFI/BOOT/grub.cfg, but this is red herring; this file isn’t used by the installer. You need to find images/efiboot.img, mount it, find the grub.cfg file it contains, and modify a boot entry to point to the kickstart disk. Unmount, build your ISO, and the installer will now boot properly on a UEFI machine.
You have added “repo” entries for ExtraPackages and AppStream in kickstart and never referred it in %packages section. So will all the packages in both these folders get installed automatically?
We don’t need to refer them again in
%packages
. Kickstart will automatically search for rpms from%packages
inside the repos you have added manually usingrepo
entriesYou mean I should still list the rpm names inside %packages directive?
If I want all the packages of ExtraPackages to be installed on my system without having to list each one of them manually?
I don’t think there is a straight forward way to install all packages from a repo without defining either package or group name.
You could try using
yum --disablerepo=* --enablerepo=myrepo install -y *
as part of%post
script (not tested)