To minimize the impact of downtime, you can consider deploying once a day on weekdays outside of peak hours. This schedule can be configured easily using the GitHub action itself. git add .github/workflows/docker-publish.yml
git commit -m "Add GitHub Actions workflow for Docker build and deploy"
git push
Are you tired of manually building, testing, and deploying your applications?docker run hello-world
That means more time building, testing, and shipping better software.While GitHub Actions handles the automation of building your Docker images and triggering deployment scripts, manually configuring, securing, monitoring, and updating servers can be complex and time-consuming.
Table of Contents
- 1 What are GitHub Actions?
- 2 Steps to Deploy Docker Container with GitHub Actions for CI/CD
- 2.1 Prerequisites:
- 2.2 Step 1: Connect to Linux Server via SSH
- 2.3 Step 2: Verify Docker Installation on the Server
- 2.4 Step 3: Add GitHub Action Secrets for SSH Access
- 2.5 Step 4: Configure GitHub Actions Workflow
- 2.6 📖 Suggested read: Docker Security: Best Practices to Secure a Docker Container
- 2.7 Step 5: Commit Workflow and Trigger Deployment
- 2.8 Step 6: Remove Unused Docker Resources (Optional)
- 3 Wrapping Up: Who Should Use Docker with GitHub Actions for CI/CD?
- 4 FAQs on Docker with GitHub Actions for CI/CD
- 4.1 What are the advantages of using Docker for CI/CD?
- 4.2 How do I secure my Docker images in GitHub Actions?
- 4.3 Can I use Docker Compose with GitHub Actions?
- 4.4 What is the best way to manage secrets in GitHub Actions?
- 4.5 Is Docker necessary for CI/CD?
- 4.6 How does Docker improve CI/CD pipelines?
- 4.7 Can I use self-hosted runners with Docker in GitHub Actions?
- 4.8 What is the difference between Docker and Kubernetes for CI/CD?
What are GitHub Actions?
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
...
UV7ErwUhELMZFrAAAAE3RhdHRpY29kZXJAc3Rhcmx1c3QBAgMEBQYH
-----END OPENSSH PRIVATE KEY-----
Navigate to your GitHub repository > Settings > Secrets and variables > Actions. Click “New repository secret” for each of the following:
Key Features of GitHub Actions
Now, define the CI/CD pipeline using a GitHub Actions workflow file. This file tells GitHub what steps to perform when triggered (e.g., on a push to your main branch).By the end of this tutorial, you will be able to configure a deployment pipeline that updates your live server in under a minute after you push changes to it.Docker ensures consistent environments from development through production. It provides process isolation and ensures that builds and tests don’t interfere with each other or the host system dependencies. Paste the following code into .github/workflows/docker-publish.yml
:
Steps to Deploy Docker Container with GitHub Actions for CI/CD
⚠️ Warning: The deployment script uses docker stop and docker rm
before docker run. This means there will be a brief moment of downtime while the container is replaced. For zero-downtime deployments, more advanced strategies like blue-green deployments or using orchestration tools like Kubernetes are needed, which are beyond this basic setup.
Prerequisites:
- GitHub Repository: You need a GitHub repository containing all your application code. Make sure you have committed and pushed your latest code changes to GitHub.
- Dockerfile: You must have a Dockerfile in the root of your repository. This file contains the step-by-step instructions Docker uses to build an image of your application. The specific commands inside the Dockerfile depend heavily on your application’s language, framework, and dependencies (e.g., installing packages, copying code, setting entry points), so we assume you have already created a functional Dockerfile.
Step 1: Connect to Linux Server via SSH
You can absolutely use self-hosted runners with Docker in GitHub Actions. This approach gives you complete control over the build environment and resources.📖 Suggested read: How To Create a Docker Image For Your ApplicationWhen you use a CI/CD pipeline, you will quickly end up with a large number of old obsolete containers on your server. These containers are often not needed and can slowly fill your disk space. If you are running low on disk space, then you can add an optional cleanup step to your deployment script to prevent your server’s disk space from filling up with old, unused Docker images, containers, and ‘build cache’.
Step 2: Verify Docker Installation on the Server
name: Publish & Deploy Docker container
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
on:
push:
# Adjust branch name if needed (e.g., master, production)
branches: [ "main" ]
# Publish semver tags as releases.
tags: [ 'v*.*.*' ]
env:
# Use docker.io for Docker Hub if empty
REGISTRY: ghcr.io
# github.repository as <account>/<repo>
IMAGE_NAME: ${{ github.repository }}
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
# This is used to complete the identity challenge
# with sigstore/fulcio when running outside of PRs.
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
# Install the cosign tool except on PR
# https://github.com/sigstore/cosign-installer
- name: Install cosign
if: github.event_name != 'pull_request'
uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 #v3.5.0
with:
cosign-release: 'v2.2.4'
# Set up BuildKit Docker container builder to be able to build
# multi-platform images and export cache
# https://github.com/docker/setup-buildx-action
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
# Login against a Docker registry except on PR
# https://github.com/docker/login-action
- name: Log into registry ${{ env.REGISTRY }}
if: github.event_name != 'pull_request'
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# Extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
# Build and push Docker image with Buildx (don't push on PR)
# https://github.com/docker/build-push-action
- name: Build and push Docker image
id: build-and-push
uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
# Sign the resulting Docker image digest except on PRs.
# This will only write to the public Rekor transparency log when the Docker
# repository is public to avoid leaking data. If you would like to publish
# transparency data even for private images, pass --force to cosign below.
# https://github.com/sigstore/cosign
- name: Sign the published Docker image
if: ${{ github.event_name != 'pull_request' }}
env:
# https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-an-intermediate-environment-variable
TAGS: ${{ steps.meta.outputs.tags }}
DIGEST: ${{ steps.build-and-push.outputs.digest }}
# This step uses the identity token to provision an ephemeral certificate
# against the sigstore community Fulcio instance.
run: echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST}
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- name: SSH and Deploy to Server
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SERVER_IP }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SERVER_PRIVATE_SSH_KEY }}
port: ${{ secrets.SERVER_PORT }}
script: |
echo ${{ secrets.GITHUB_TOKEN }} | docker login ${{ env.REGISTRY }} -u ${{ github.actor }} --password-stdin
echo "--- Pulling latest Docker image ---"
docker pull ghcr.io/tatticoder/terraform-get-time:main
echo "--- Stopping existing container (if running) ---"
docker stop my_container
echo "--- Removing existing container ---"
docker rm my_container
echo "--- Starting new container ---"
# Adjust ports (-p host:container)
# Add any necessary environment variables (-e)
docker run -d --name my_container -p 3000:80 ghcr.io/tatticoder/terraform-get-time:main
Ready to take the work out of server management? Start with RunCloud today.First, ensure you can connect to the target Linux server where your Docker container will run. You’ll need SSH access for this. Docker drastically improves CI/CD pipelines by guaranteeing environment consistency across all stages, from developer laptops to production servers. Its containerization isolates dependencies, preventing conflicts and simplifying the configuration of build agents.
Step 3: Add GitHub Action Secrets for SSH Access
Yes, Docker Compose can be effectively used within GitHub Actions workflows to manage multi-container setups. You simply need to ensure Docker Compose is installed on the runner, then use standard docker-compose commands to build images or spin up services like databases for integration testing.Start by scanning your images for vulnerabilities using container scanning tools directly within your GitHub Actions workflow. To reduce the attack surface, use minimal, trusted base images and avoid installing unnecessary packages. Always configure containers to run as non-root users and manage secrets securely using GitHub Secrets, never embedding them in the image layers.

- SERVER_IP: The public IP address of your Linux server.
- SERVER_PORT: The SSH port for your server (usually 22, but might be different if customized).
- SERVER_USER: The username you use to log in to your server via SSH. We strongly recommend creating a new user account specifically for this deployment step and giving it appropriate permissions for better security.
- SERVER_PRIVATE_SSH_KEY: The entire content of the private SSH key file that corresponds to a public key authorized on your server. Your private SSH key should look something like this:
📖 Suggested read: Understanding Docker Services | RunCloud Docsssh your_server_user@your_server_ip
The following command will automatically remove any Docker resources (like stopped containers and images not associated with a running container) that haven’t been used in the last 24 hours without requiring confirmation. Running this periodically helps maintain server health and efficiently reclaim storage:
Step 4: Configure GitHub Actions Workflow
Once connected to your server via SSH, you need to confirm that Docker is installed and running correctly. The Docker Command Line Interface (CLI) is required to pull images and run containers. You can test this by running the standard Docker test image:docker system prune --filter "until=24h" --force
Create a YAML file named docker-publish.yml
inside your repository’s .github/workflows/
directory. You can create this directory and file directly via the GitHub web interface or in your local repository using a text editor, and then commit and push the changes.Modern Continuous Integration (CI) and Continuous Deployment (CD) approaches can automatically trigger a deployment pipeline to build your Docker image, run tests, push it to a container registry like GHCR or Docker Hub, and deploy it to your server.
- name: Sets the display name for your workflow in the GitHub Actions tab.
- on: push: branches: [ main ]: This triggers the workflow every time code is pushed to the main branch. You can change main to your default or production branch name (e.g., master).
- Build Job: This section handles the process of creating and publishing the Docker container.
- Checkout code: Uses the standard actions/checkout action to get your repository code onto the runner.
- Log in to GHCR: Uses docker/login-action to authenticate with GitHub Container Registry using the automatically generated GITHUB_TOKEN.
- Build and push: Uses docker/build-push-action.
- Deploy via SSH Job: This job uses the popular SSH Remote Commands action to connect to your server using the secrets you configured and execute commands.
The best part is that you can complete all of this in less than a minute after pushing code to your GitHub repository.
📖 Suggested read: Docker Security: Best Practices to Secure a Docker Container
Step 5: Commit Workflow and Trigger Deployment
To allow your GitHub Actions workflow to securely log in to your server and execute deployment commands, you must store your server’s connection details as encrypted secrets in your GitHub repository. You should never hardcode sensitive information directly into your workflow file – we recommend using GitHub’s built-in secret management system for this.All the actions and workflows on GitHub can be reused. You can even configure pre-built steps created by the community (available in the GitHub Marketplace) and save significant development time. Lastly, GitHub Actions includes integrated secrets management for securely handling sensitive information like API keys and passwords. It provides live logs for monitoring workflow progress in real time and the ability to store artifacts like build outputs or test reports.The most secure and recommended method is using GitHub Actions encrypted secrets, which are configured at the repository or organization level. These secrets can then be safely accessed within your workflow as environment variables or passed to specific actions needing credentials. Never hardcode sensitive information directly in your workflow files or application code checked into version control.

RunCloud provides a clean, efficient way to manage servers without the usual hassle. From provisioning and security hardening to database setup and app deployment, it streamlines the tasks that slow developers down. You stay in control of your infrastructure, but without getting buried in configuration files or command-line firefighting.
Step 6: Remove Unused Docker Resources (Optional)
Finally, save the docker-publish.yml
file. If you created it locally, commit it and push it to your GitHub repository using the following commands:GitHub Actions has several useful features that make it a compelling choice for automation. Firstly, its event-driven nature allows workflows to trigger automatically in response to a wide variety of GitHub events. Secondly, matrix builds let you efficiently test your code across different environments simultaneously; you can define combinations of operating systems (like Linux, macOS, and Windows), software versions (like different Node.js or Python versions), or other variables, and GitHub Actions will run a job for each combination.Again, consulting the RunCloud documentation can be very helpful in generating and managing SSH keys securely, especially regarding best practices for SSH security.
Wrapping Up: Who Should Use Docker with GitHub Actions for CI/CD?
Docker is primarily used within the CI/CD pipeline to build application images, run tests in isolated containerized environments, and consistently package dependencies. Kubernetes is a container orchestrator typically acting as the deployment target after the CI pipeline, responsible for managing the runtime, scaling, and health of containers in a cluster. Docker creates the portable application packages used in CI, while Kubernetes manages fleets of those packages in production or staging environments (CD).While not strictly mandatory, Docker offers substantial benefits that make it a highly popular choice for modern CI/CD pipelines. While alternative options are available, Docker provides reproducible and isolated build/test environments that are useful for reliable continuous integration and delivery.Let’s walk through the steps to automate building your application’s Docker image, pushing it to a registry, and then deploying it to your server every time you push changes to your repository.📖 Suggested read: What is Docker And How Does it WorkBy combining Docker with the automation capabilities of GitHub Actions, you can create a fast and effective DevOps pipeline. Docker ensures your application runs the same way everywhere by packaging it with its dependencies in a portable Docker container based on instructions in your Dockerfile. GitHub Actions then automates the build and push steps, securely manages secrets like access tokens, and handles the final deployment to your infrastructure.