Home Technical Support Docker Image Declaration, Seccomp Profiles, and Handling Failures in GitHub Actions

Docker Image Declaration, Seccomp Profiles, and Handling Failures in GitHub Actions

Last updated on Jan 06, 2026

Docker Image Declaration, Seccomp Profiles, and Handling Failures in GitHub Actions

Learn why certain Docker image declarations don’t work in CI/CD pipelines, how seccomp filters affect chown/chmod, and the best ways to allow failures in GitHub Actions.


Introduction

When you start building DevSecOps pipelines, you quickly discover that small configuration details can have a big impact on security and reliability. This article explains three common stumbling blocks:

  1. Why you can’t declare a Docker image up‑front for certain hysnsec images – the difference between static image pulls and dynamic builds in GitLab CI.
  2. How Seccomp profiles influence system calls such as chown and chmod, and why those calls sometimes succeed and sometimes fail.
  3. How to let a step or job fail gracefully in GitHub Actions using continue-on-error and conditional expressions.

By the end of the guide you’ll be able to write more robust CI/CD definitions, troubleshoot Seccomp‑related errors, and keep your pipelines running even when non‑critical steps break.


1. Declaring Docker Images in GitLab CI – Why “Up‑Front” Doesn’t Always Work

1.1 What “declare the image up‑front” means

In a .gitlab-ci.yml file you can specify an image in two ways:

# 1️⃣ Static declaration (up‑front)
image: hysnsec/bandit:latest

# 2️⃣ Dynamic declaration (inside a job)
job_name:
  script:
    - docker run --rm -v $(pwd):/src hysnsec/bandit -r /src -f json -o /src/bandit-output.json

The first approach tells GitLab Runner to pull the image once before any job starts. The second runs the image inside the job’s script.

1.2 Why the static approach fails for some hysnsec images

Reason Explanation
Image requires runtime arguments Many hysnsec images expect volume mounts (-v $(pwd):/src) or environment variables that are only known at job execution time. Declaring the image alone provides none of these, causing the container to exit immediately.
Custom entrypoint logic Some images replace the default entrypoint with a wrapper script that expects parameters (e.g., a path to scan). Without those parameters the container cannot start correctly.
Security restrictions GitLab’s shared runners may block privileged operations required by the image when it is pulled as the default environment. Running the image explicitly inside script lets you add --privileged or other flags if needed.

Bottom line: Use a static image: declaration only when the container can run without additional runtime configuration. For security‑focused images like those from hysnsec, it’s safer to invoke them inside the job’s script.

1.3 Quick reference – When to use each style

  • Static image: – Simple unit tests, language runtimes, linting tools that need no extra mounts.
  • Dynamic docker run – Scanners, build tools, or any image that requires volumes, environment variables, or special flags.

2. Seccomp Profiles – Why chown and chmod May Appear to Work or Fail

Seccomp (Secure Computing Mode) filters the system calls a container can execute. A typical DevSecOps lab will provide a custom Seccomp JSON that blocks risky calls while allowing the rest.

2.1 Understanding the behavior

Scenario Seccomp rule Observed result Why it happens
mkdir blocked mkdir syscall denied adduser fails at directory creation The user‑creation flow tries to create /home/abc. The blocked mkdir aborts the process, producing “Operation not permitted”.
chown blocked chown syscall denied adduser fails after directory is created The home directory is created, but the subsequent chown 1000:1000 /home/abc is blocked, causing the same error message.
chmod blocked chmod syscall denied adduser fails during permission change After mkdir, the tool attempts chmod 488 /home/abc. The denied syscall stops the flow.

When the Seccomp profile does not block chown or chmod, those calls succeed because the container’s process has the required capabilities (usually root inside the container).

2.2 Practical example

# Inside a container with a restrictive seccomp profile
root@container:/# adduser abc
Adding user `abc' ...
Creating home directory `/home/abc' ...
Stopped: chown 1000:1000 /home/abc: Operation not permitted

If you remove the chown rule from the profile:

{
  "syscalls": [
    { "name": "chmod", "action": "SCMP_ACT_ALLOW" },
    { "name": "mkdir", "action": "SCMP_ACT_ALLOW" }
    // Note: chown is omitted → default allow
  ]
}

the same command succeeds because the kernel no longer blocks the chown syscall.

2.3 Tips for working with Seccomp

  1. Start with a permissive profile (SCMP_ACT_ALLOW for everything) and iteratively add blocks.
  2. Log denied syscalls – run the container with --security-opt seccomp=unconfined and inspect dmesg to see which calls are being filtered.
  3. Remember ownership matterschmod works for the file owner. If you run as root, you can always change permissions; non‑root users will be blocked by the profile.
  4. Test with real user‑creation commands (useradd, adduser) to verify that the profile doesn’t unintentionally break provisioning steps.

3. Allowing Failures in GitHub Actions – continue-on-error and Conditional Execution

In CI pipelines, some steps are “nice‑to‑have” (e.g., security scans) and should not break the whole workflow. GitHub Actions provides two mechanisms:

3.1 continue-on-error – Mark a step or job as successful even if it fails

sast:
  runs-on: ubuntu-20.04
  needs: test
  steps:
    - uses: actions/checkout@v2
    - name: Run Bandit scan
      run: |
        docker run --rm -v $(pwd):/src hysnsec/bandit -r /src -f json -o /src/bandit-output.json
      continue-on-error: true   # <-- step never fails the job
    - name: Upload results
      uses: actions/upload-artifact@v2
      with:
        name: Bandit
        path: bandit-output.json
      if: always()               # ensures artifact upload runs even if previous step failed

Result: The job finishes with a green checkmark, but the scan’s exit code is still recorded in the logs.

3.2 if: always() – Run a step regardless of previous outcome

Useful for cleanup or artifact collection:

- name: Cleanup temporary files
  run: rm -rf /tmp/tmpfile
  if: always()

3.3 Combining both for a “soft‑fail” job

sast:
  runs-on: ubuntu-20.04
  needs: test
  continue-on-error: true   # entire job never fails the workflow
  steps:
    - uses: actions/checkout@v2
    - run: docker run --rm -v $(pwd):/src hysnsec/bandit -r /src -f json -o /src/bandit-output.json
    - uses: actions/upload-artifact@v2
      with:
        name: Bandit
        path: bandit-output.json
      if: always()

3.4 When to use each approach

Situation Recommended setting
Optional security scan – you want a report but don’t want to block the pipeline continue-on-error: true on the scan step
Mandatory build step – failure must stop the pipeline Omit continue-on-error; use default behavior
Always‑run cleanup – regardless of success/failure if: always() on the cleanup step

Common Questions & Quick Tips

  • Q: Can I override a Seccomp profile at runtime?
    A: Yes. Use --security-opt seccomp=/path/to/profile.json with docker run. For GitHub Actions, add it to the run command.

  • Q: Why does my GitLab job still fail even with continue-on-error?
    A: continue-on-error is a GitHub Actions feature. In GitLab you need allow_failure: true on the job definition.

  • Tip: When testing Seccomp changes, run the container with --security-opt seccomp=unconfined first to confirm that the issue is truly a blocked syscall.

  • Tip: Keep your GitHub Actions YAML tidy by extracting reusable steps into reusable workflows or composite actions – especially when you repeatedly apply continue-on-error.


Conclusion

Understanding the nuances of Docker image declaration, Seccomp filtering, and failure handling makes your DevSecOps pipelines both secure and resilient. Declare images dynamically when they need runtime parameters, fine‑tune Seccomp profiles to block only the truly dangerous syscalls, and use continue-on-error together with conditional if: always() to keep non‑critical steps from derailing the entire workflow. Apply these best practices, and you’ll spend less time debugging and more time delivering secure software.