Packaging and Automation of Docker Linux Apps

What this guide includes:
  • 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.

What might be missing or assumed:
  • 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.

Step 1: Organize Your Repository Structure

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)

usr/local/bin/mydockerapp

#!/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

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

  • DEBIAN/control

Package: mydockerapp
Version: 1.0-1
Architecture: amd64
Maintainer: Your Name <[email protected]>
Depends: docker-ce
Description: MyDockerApp Docker container as systemd service
  • DEBIAN/postinst

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

#!/bin/bash
systemctl stop mydockerapp.service || true
docker stop mydockerapp || true
docker rm mydockerapp || true
  • Optional DEBIAN/postrm

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

Make executable: chmod +x DEBIAN/*


Step 5: RPM Packaging Files

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 <[email protected]> - 1.0-1
- Initial package

Step 6: Arch Linux PKGBUILD Example

pacman/PKGBUILD

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:

makepkg -si

Step 7. Package Repository Setup and GPG Key Management Guide

Prerequisite: Install GPG on Client Machines

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

Install Nginx or Apache2 to serve your package repositories:

# 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

# 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:

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

# 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:

# Generate new key (recommended RSA 4096 bits)
gpg --full-generate-key

List keys and get your key ID:

gpg --list-keys

Export and Publish Your GPG Public Key for Clients

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

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:

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:

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

For RPM repositories:

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:

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:

sudo a2ensite repo
sudo systemctl restart apache2

Client Configuration for Multiple Linux Distributions

Verify GPG Availability (Usually Preinstalled)

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

gpg --version

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

  • Debian/Ubuntu:

    sudo apt update
    sudo apt install gnupg -y
  • RHEL/CentOS/Fedora:

    sudo yum install gnupg2 -y
  • Arch Linux:

    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:

    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:

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

    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:

    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:

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

    Or on Fedora with dnf:

    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:

    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

APT:

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:

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:

sudo pacman -Sy mydockerapp

Step 9: Automate with GitHub Actions

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

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

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)

.gitlab-ci.yml

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 [email protected]:/var/www/html/apt-repo/pool/main/
    - ssh [email protected] 'reprepro -b /var/www/html/apt-repo includedeb stable mydockerapp.deb'
    - scp ~/rpmbuild/RPMS/x86_64/mydockerapp-*.rpm [email protected]:/var/www/html/yumrepo/mydockerapp/
    - ssh [email protected] 'createrepo --update /var/www/html/yumrepo/'

Final Tips for Beginners

  • 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.


References

Last updated