# Packaging and Automation of Docker Linux Apps

<details>

<summary>What this guide includes:</summary>

* Detailed repo structure with required files for .deb, .rpm, and Arch packages.
* Exact example scripts for Docker container lifecycle management.
* Systemd service unit for container automatic startup/control.
* Debian and RPM packaging metadata and maintainer scripts.
* Arch PKGBUILD file with build/install steps.
* Hosting repository server setup, HTTPS, and Let’s Encrypt for SSL.
* GPG key generation, exporting, signing repository metadata, and client key configuration.
* User installation commands for apt, dnf, and pacman.
* Complete GitHub Actions and GitLab CI/CD workflows for Docker images and Linux packages.

</details>

<details>

<summary>What might be missing or assumed:</summary>

* Some basic Docker skills (writing a `Dockerfile`) are assumed before packaging.
* Basic Linux command line and shell scripting knowledge.
* Details on deployment environments, infrastructure for hosting repos, and how to provision that hardware or cloud VM.
* Advanced package signing practices for very secure environments.
* How to test installation in clean VMs/containers with specific examples.
* Handling multi-architecture builds and cross-building packages.
* More in-depth security best practices beyond GPG.

**Recommendations for true beginners:**

* Learn Docker basics separately if unfamiliar.
* Familiarize with Linux package management basics on your target distros.
* Practice writing Dockerfiles and building images locally.
* Set up a simple web server with HTTPS and practice hosting static files.
* Read introductory guides on GPG usage, key management, and secure shell scripting.

</details>

## Step 1: Organize Your Repository Structure <a href="#step-1-organize-your-repository-structure" id="step-1-organize-your-repository-structure"></a>

```
mydockerapp/
├── DEBIAN/                      # Debian packaging files
│   ├── control                 # Package metadata and dependencies
│   ├── postinst                # Post-install script pulling docker image, enabling service
│   ├── prerm                   # Pre-removal script stopping service & container
│   └── postrm                  # Optional cleanup script
├── rpm/                        # RPM packaging files (Fedora, Amazon Linux, CentOS)
│   └── mydockerapp.spec         # Spec file for RPM packaging
├── pacman/                     # Arch packaging files
│   └── PKGBUILD                # Arch package build script
├── usr/local/bin/
│   └── mydockerapp             # Executable script to manage Docker container lifecycle
├── etc/systemd/system/
│   └── mydockerapp.service     # systemd service unit managing the Docker container lifecycle
├── opt/mydockerapp/
│   ├── README.md               # Documentation
│   └── docker-compose.yml      # Optional docker-compose file
├── .github/workflows/
│   ├── build-and-push-image.yml   # GitHub Actions (Docker image)
│   └── build-packages.yml          # GitHub Actions (Linux packages)
├── .gitlab-ci.yml              # Optional GitLab CI/CD config
├── LICENSE
└── README.md
```

***

## Step 2: Docker Management Script (Executable) <a href="#step-2-docker-management-script-executable" id="step-2-docker-management-script-executable"></a>

`usr/local/bin/mydockerapp`

```bash
#!/bin/bash
case "$1" in
  start)
    docker run -d --name mydockerapp yourdockerhubusername/yourproject:latest
    ;;
  stop)
    docker stop mydockerapp || true
    docker rm mydockerapp || true
    ;;
  restart)
    $0 stop
    $0 start
    ;;
  logs)
    docker logs -f mydockerapp
    ;;
  *)
    echo "Usage: $0 {start|stop|restart|logs}"
    exit 1
    ;;
esac
```

***

## Step 3: systemd Service Unit <a href="#step-3-systemd-service-unit" id="step-3-systemd-service-unit"></a>

`etc/systemd/system/mydockerapp.service`

```
[Unit]
Description=MyDockerApp Docker Container Service
After=docker.service
Requires=docker.service

[Service]
ExecStart=/usr/local/bin/mydockerapp start
ExecStop=/usr/local/bin/mydockerapp stop
Restart=always

[Install]
WantedBy=multi-user.target
```

***

## Step 4: Debian Packaging Files <a href="#step-4-debian-packaging-files" id="step-4-debian-packaging-files"></a>

* `DEBIAN/control`

```
Package: mydockerapp
Version: 1.0-1
Architecture: amd64
Maintainer: Your Name <you@example.com>
Depends: docker-ce
Description: MyDockerApp Docker container as systemd service
```

* `DEBIAN/postinst`

```bash
#!/bin/bash
set -e
docker pull yourdockerhubusername/yourproject:latest
systemctl enable mydockerapp.service
systemctl start mydockerapp.service
```

* `DEBIAN/prerm`

```bash
#!/bin/bash
systemctl stop mydockerapp.service || true
docker stop mydockerapp || true
docker rm mydockerapp || true
```

* Optional `DEBIAN/postrm`

```bash
#!/bin/bash
docker rmi yourdockerhubusername/yourproject:latest || true
```

Make executable: `chmod +x DEBIAN/*`

***

## Step 5: RPM Packaging Files <a href="#step-5-rpm-packaging-files" id="step-5-rpm-packaging-files"></a>

`rpm/mydockerapp.spec`

```
Name: mydockerapp
Version: 1.0
Release: 1%{?dist}
Summary: MyDockerApp Docker container service

License: MIT
URL: https://github.com/yourusername/mydockerapp
Requires: docker-ce

%description
MyDockerApp packaged as a Docker container and managed with systemd.

%prep

%build

%install
mkdir -p %{buildroot}/usr/local/bin
cp ../usr/local/bin/mydockerapp %{buildroot}/usr/local/bin/
cp ../etc/systemd/system/mydockerapp.service %{buildroot}/etc/systemd/system/

%post
docker pull yourdockerhubusername/yourproject:latest
systemctl enable mydockerapp.service
systemctl start mydockerapp.service

%preun
systemctl stop mydockerapp.service || true
docker stop mydockerapp || true
docker rm mydockerapp || true

%files
/usr/local/bin/mydockerapp
/etc/systemd/system/mydockerapp.service

%changelog
* $(date +"%a %b %d %Y") Your Name <you@example.com> - 1.0-1
- Initial package
```

***

## Step 6: Arch Linux PKGBUILD Example <a href="#step-6-arch-linux-pkgbuild-example" id="step-6-arch-linux-pkgbuild-example"></a>

`pacman/PKGBUILD`

```bash
pkgname=mydockerapp
pkgver=1.0
pkgrel=1
pkgdesc="MyDockerApp packaged as Docker container service"
arch=('x86_64')
url="https://github.com/yourusername/mydockerapp"
license=('MIT')
depends=('docker')
source=()
md5sums=()

package() {
  install -Dm755 ../usr/local/bin/mydockerapp "$pkgdir/usr/local/bin/mydockerapp"
  install -Dm644 ../etc/systemd/system/mydockerapp.service "$pkgdir/etc/systemd/system/mydockerapp.service"
}
```

Build with:

```bash
makepkg -si
```

***

## Step 7. Package Repository Setup and GPG Key Management Guide <a href="#package-repository-setup-and-gpg-key-management-gu" id="package-repository-setup-and-gpg-key-management-gu"></a>

### Prerequisite: Install GPG on Client Machines <a href="#prerequisite-install-gpg-on-client-machines" id="prerequisite-install-gpg-on-client-machines"></a>

Before clients can import and verify your repository’s GPG key, they must have GPG installed:

* Debian/Ubuntu clients:

  ```
  sudo apt update
  sudo apt install gnupg -y
  ```
* RHEL/CentOS clients:

  ```
  sudo yum install gnupg2 -y
  ```
* SUSE/OpenSUSE clients:

  ```
  sudo zypper install gpg2 -y
  ```

### Install Web Server Software on the public-facing linux server <a href="#id-1-install-web-server-software-on-your-vps" id="id-1-install-web-server-software-on-your-vps"></a>

Install Nginx or Apache2 to serve your package repositories:

```bash
# Debian/Ubuntu:
sudo apt update
sudo apt install nginx apache2 -y

# RHEL/CentOS/AlmaLinux/Rocky:
sudo yum install nginx httpd -y

# SUSE/OpenSUSE:
sudo zypper install nginx apache2 -y
```

**Open Firewall Ports for HTTP/HTTPS**

```bash
# Debian/Ubuntu (UFW):
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

# RHEL/CentOS/SUSE (firewalld):
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
```

**Create Repository Directory and Set Permissions**

Based on your web server:

* Apache default: `/var/www/html/repo/`
* Nginx default: `/usr/share/nginx/html/repo/`

Create directory and set ownership:

```bash
sudo mkdir -p /var/www/html/repo

# Debian/Ubuntu Apache/Nginx
sudo chown -R www-data:www-data /var/www/html/repo

# RHEL/CentOS Apache (httpd)
sudo chown -R apache:apache /var/www/html/repo
```

**Install Repository Management Tools**

```bash
# Debian/Ubuntu (APT repositories)
sudo apt install reprepro -y

# RHEL/CentOS (YUM/DNF repositories)
sudo yum install createrepo -y

# SUSE/OpenSUSE (Zypper repositories)
sudo zypper install createrepo -y
```

**Generate GPG Key on the Server**

If you haven’t created a GPG key yet, generate one now:

<pre class="language-bash"><code class="lang-bash"><strong># Generate new key (recommended RSA 4096 bits)
</strong>gpg --full-generate-key
</code></pre>

List keys and get your key ID:

```bash
gpg --list-keys
```

**Export and Publish Your GPG Public Key for Clients**

Export your public key as ASCII armor file for client download:

```bash
gpg --armor --export "Your Name or Email" > /var/www/html/repo/public-key.asc
sudo chmod 644 /var/www/html/repo/public-key.asc
```

Also create a de-armored binary version for server use, if needed:

```bash
gpg --dearmor /var/www/html/repo/public-key.asc
sudo mv public-key.gpg /usr/share/keyrings/yourrepo.gpg
sudo chmod 644 /usr/share/keyrings/yourrepo.gpg
```

**Configure Repository Manager to Use GPG for Signing Packages**

Create `conf/distributions` file in your APT repository directory:

```
Origin: YourRepoName
Label: YourRepoLabel
Codename: your-distro-codename
Architectures: amd64
Components: main
Description: Your repository description
SignWith: your-gpg-key-id
```

**Add and sign packages:**

```bash
reprepro -b /var/www/html/repo includedeb your-distro-codename /path/to/package.deb
```

For RPM repositories:

```bash
rpm --addsign package.rpm
createrepo /path/to/repo/
```

**Configure the Web Server to Serve Your Repository**

**Nginx configuration at `/etc/nginx/conf.d/repo.conf`:**

```
server {
    listen 80;
    server_name your.repo.domain;

    root /var/www/html/repo;
    autoindex on;

    location ~ /(.*)/conf { deny all; }
    location ~ /(.*)/db { deny all; }
    location ~ /(.*)/incoming { deny all; }
}
```

Test and reload Nginx:

```bash
sudo nginx -t
sudo systemctl reload nginx
```

**Apache configuration at `/etc/apache2/sites-available/repo.conf`:**

```
<VirtualHost *:80>
    ServerName your.repo.domain
    DocumentRoot /var/www/html/repo

    <Directory /var/www/html/repo>
        Options Indexes FollowSymLinks
        AllowOverride None
        Require all granted
    </Directory>

    <Directory /var/www/html/repo/*/db> Require all denied </Directory>
    <Directory /var/www/html/repo/*/conf> Require all denied </Directory>
    <Directory /var/www/html/repo/*/incoming> Require all denied </Directory>
</VirtualHost>
```

Enable site and restart Apache:

```bash
sudo a2ensite repo
sudo systemctl restart apache2
```

### Client Configuration for Multiple Linux Distributions <a href="#client-configuration-for-multiple-linux-distributi" id="client-configuration-for-multiple-linux-distributi"></a>

#### Verify GPG Availability (Usually Preinstalled)

Most Linux distributions include GPG by default. Verify GPG is available by running:

```bash
gpg --version
```

If this command errors, manually install GPG with your package manager:

* Debian/Ubuntu:

  ```bash
  sudo apt update
  sudo apt install gnupg -y
  ```
* RHEL/CentOS/Fedora:

  ```bash
  sudo yum install gnupg2 -y
  ```
* Arch Linux:

  ```bash
  sudo pacman -Sy gnupg --noconfirm
  ```

This step is generally unnecessary on modern systems.

### Import the Repository's Public GPG Key

* Download the public key from the repository server and import it for package verification.
* **Debian/Ubuntu**:

  ```bash
  wget http://your.repo.domain/public-key.asc
  gpg --dearmor public-key.asc
  sudo mv public-key.gpg /usr/share/keyrings/public-key.gpg
  ```
* **RHEL/CentOS/Fedora**:

  ```bash
  sudo rpm --import http://your.repo.domain/public-key.asc
  ```
* **Arch Linux**:

  ```bash
  curl -O http://your.repo.domain/public-key.asc
  gpg --import public-key.asc
  ```

### Configure the Package Manager to Use Your Repository

**Debian/Ubuntu (APT)**

* Create a source list file `/etc/apt/sources.list.d/yourrepo.list`:

  ```
  deb [arch=amd64 signed-by=/usr/share/keyrings/public-key.gpg] http://your.repo.domain/ your-distro-codename main
  ```
* Update package cache and install packages:

  ```bash
  sudo apt update
  sudo apt install your-package-name
  ```

**RHEL/CentOS/Fedora (YUM/DNF)**

* Create a repo file `/etc/yum.repos.d/yourrepo.repo`:

  ```
  [yourrepo]
  name=Your Repo Name
  baseurl=http://your.repo.domain/
  enabled=1
  gpgcheck=1
  gpgkey=http://your.repo.domain/public-key.asc
  ```
* Clean metadata cache and install packages:

  ```bash
  sudo yum clean all
  sudo yum install your-package-name
  ```

  Or on Fedora with dnf:

  ```bash
  sudo dnf clean all
  sudo dnf install your-package-name
  ```

**Arch Linux (Pacman)**

* Add the repository to `/etc/pacman.conf`:

  ```
  [yourrepo]
  SigLevel = Optional TrustAll
  Server = http://your.repo.domain/$arch
  ```
* Update package database and install:

  ```bash
  sudo pacman -Sy your-package-name
  ```

### Verify Package Installation

After configuration, you can verify package installation and trust:

* Check package signature verification errors (APT/YUM/DNF/Pacman handle this automatically if configured).
* Use standard package manager commands to confirm package is correctly installed.

***

## Step 8: User Installation Commands <a href="#step-9-user-installation-commands" id="step-9-user-installation-commands"></a>

**APT:**

```bash
curl -fsSL https://yourrepo.example.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/yourrepo.gpg
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/yourrepo.gpg] https://yourrepo.example.com stable main" | sudo tee /etc/apt/sources.list.d/yourrepo.list
sudo apt update
sudo apt install mydockerapp
sudo systemctl status mydockerapp
```

**DNF/YUM:**

```bash
sudo tee /etc/yum.repos.d/yourrepo.repo <<EOF
[yourrepo]
name=Your Repo
baseurl=https://yourrepo.example.com/
enabled=1
gpgcheck=1
gpgkey=https://yourrepo.example.com/gpg
EOF

sudo dnf makecache
sudo dnf install mydockerapp
sudo systemctl start mydockerapp
```

**Pacman:**

```bash
sudo pacman -Sy mydockerapp
```

***

## Step 9: Automate with GitHub Actions <a href="#step-10-automate-with-github-actions" id="step-10-automate-with-github-actions"></a>

`.github/workflows/build-and-push-image.yml`

```yaml
name: Build and Publish Docker Image

on:
  push:
    branches: [main]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build-and-push:
    runs-on: ubuntu-latest

    permissions:
      contents: read
      packages: write
      id-token: write

    steps:
      - uses: actions/checkout@v5
      
      - uses: docker/login-action@v2
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - id: meta
        uses: docker/metadata-action@v4
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}

      - uses: docker/build-push-action@v4
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
```

`.github/workflows/build-packages.yml`

```yaml
name: Build and Publish Packages

on:
  push:
    branches: [main]

jobs:
  build-packages:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v5

      - name: Install packaging tools
        run: |
          sudo apt update
          sudo apt install -y rpm dpkg-dev fakeroot

      - name: Build Debian package
        run: dpkg-deb --build mydockerapp

      - name: Build RPM package
        run: rpmbuild -ba rpm/mydockerapp.spec

      - name: Upload built packages
        uses: actions/upload-artifact@v3
        with:
          name: mydockerapp-packages
          path: |
            mydockerapp.deb
            ~/rpmbuild/RPMS/x86_64/mydockerapp-*.rpm
```

***

## Step 10: Automate with GitLab CI/CD (Optional) <a href="#step-11-automate-with-gitlab-cicd-optional" id="step-11-automate-with-gitlab-cicd-optional"></a>

`.gitlab-ci.yml`

```yaml
stages:
  - build
  - push
  - package
  - deploy

variables:
  IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG

build-image:
  stage: build
  image: docker:latest
  services:
    - docker:dind
  script:
    - docker build -t $IMAGE_TAG .
    - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY

push-image:
  stage: push
  image: docker:latest
  services:
    - docker:dind
  script:
    - docker push $IMAGE_TAG
    - docker tag $IMAGE_TAG $CI_REGISTRY_IMAGE:latest
    - docker push $CI_REGISTRY_IMAGE:latest

build-packages:
  stage: package
  image: ubuntu:22.04
  script:
    - apt-get update && apt-get install -y rpm dpkg-dev fakeroot
    - dpkg-deb --build mydockerapp
    - rpmbuild -ba rpm/mydockerapp.spec
  artifacts:
    paths:
      - mydockerapp.deb
      - ~/rpmbuild/RPMS/x86_64/mydockerapp-*.rpm

deploy:
  stage: deploy
  script:
    - scp mydockerapp.deb user@yourrepo.example.com:/var/www/html/apt-repo/pool/main/
    - ssh user@yourrepo.example.com 'reprepro -b /var/www/html/apt-repo includedeb stable mydockerapp.deb'
    - scp ~/rpmbuild/RPMS/x86_64/mydockerapp-*.rpm user@yourrepo.example.com:/var/www/html/yumrepo/mydockerapp/
    - ssh user@yourrepo.example.com 'createrepo --update /var/www/html/yumrepo/'
```

***

## Final Tips for Beginners <a href="#final-tips-for-beginners" id="final-tips-for-beginners"></a>

* Use `chmod +x` on all scripts.
* Use Let’s Encrypt SSL for hosting repos securely.
* Keep GPG private keys secure and only distribute the public key.
* Test your packages by installing on clean virtual machines.
* Use GitHub/GitLab secrets for storing tokens securely.
* Provide clear documentation in your repository’s README.

***

<h2 align="center">References</h2>

1. <https://www.infoq.com/articles/docker-application-packages-cnab/>
2. <https://dev.to/aahil13/how-to-containerize-an-application-with-docker-3i8m>
3. <https://learn.microsoft.com/en-us/dotnet/core/docker/build-container>
4. <https://docs.docker.com/get-started/workshop/02_our_app/>
5. <https://www.youtube.com/watch?v=FBZhGNCObDc>
6. [https://docker-curriculum.com](https://docker-curriculum.com/)
7. <https://docs.docker.com/build/building/best-practices/>
8. <https://jfrog.com/devops-tools/article/understanding-and-building-docker-images/>
