Skip to content

Implement CI/CD Pipeline with Production Environment (Fixes #4)#13

Open
Art4Man wants to merge 15 commits into
anvaari:mainfrom
Art4Man:main
Open

Implement CI/CD Pipeline with Production Environment (Fixes #4)#13
Art4Man wants to merge 15 commits into
anvaari:mainfrom
Art4Man:main

Conversation

@Art4Man

@Art4Man Art4Man commented Aug 22, 2025

Copy link
Copy Markdown

Summary

This PR implements a GitHub Action CI/CD pipeline that builds, pushes to Docker Hub, and deploys the BilboMusicBot to a server, addressing
issue #4.

Changes Made

GitHub Action workflow for automated CI/CD
Docker build and push to Docker Hub with multi-platform support (linux/amd64, linux/arm64)
Server deployment automation via SSH
Docker image testing to ensure bot module imports successfully

Workflow Jobs

  1. Build: Validates Docker image build and tests bot module import
  2. Release: Builds and pushes Docker images to Docker Hub (triggered on version tags)
  3. Deploy: Deploys the application to server via SSH

Triggers

  • Version tags (e.g., v1.0.0): Triggers build, push to Docker Hub, and server deployment
  • Pull requests to main: Runs build and test only
  • Main branch pushes: Runs build and test only

Required GitHub Secrets

  • DOCKER_USERNAME - Docker Hub username
  • DOCKER_PASSWORD - Docker Hub password
  • SERVER_HOST - Target server hostname/IP
  • SERVER_USER - SSH username for server
  • SERVER_SSH_KEY - SSH password for server authentication
  • BOT_TOKEN - Bot token for the application

Usage

# Create and push a version tag to trigger deployment
git tag v1.0.0
git push origin v1.0.0

This will automatically:
1. Build the Docker image
2. Test the bot module import
3. Push to Docker Hub with both latest and version-specific tags
4. Deploy to the configured server

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- New Features
- Containerized application for easier deployment.
- Docker Compose setup with persistent data and environment variable support.
- Example environment configuration to simplify onboarding.

- CI/CD
- Automated pipeline to build, test, and publish multi-architecture images on version tags.
- Automated deployment workflow with zero-downtime restart and cleanup.

- Chores
- Comprehensive ignore rules to reduce Docker build context and exclude local/IDE artifacts.
- IDE-specific ignores to keep repositories clean.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

@coderabbitai

coderabbitai Bot commented Aug 22, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Introduces Dockerization, environment configuration, and a CI/CD pipeline. Adds a Dockerfile, docker-compose, and .dockerignore; provides an .env.example; configures a GitHub Actions workflow for build/release/deploy with Docker Hub and remote server steps; adds IDE-specific ignore rules.

Changes

Cohort / File(s) Summary of Changes
Docker & Runtime
Dockerfile, docker-compose.yml, .dockerignore
Added container build definition (Python 3.13), compose service with env/volume/network, and comprehensive Docker context ignores including caches, venvs, build/test artifacts; preserves /tests/_output/.gitkeep.
CI/CD Workflow
.github/workflows/ci-cd.yml
New GitHub Actions pipeline with build (image build/test), release (multi-arch build/push, SCP), and deploy (SSH compose up, prune), triggered on main and version tags. Uses Docker Hub and remote server secrets.
Environment Template
.env.example
Sample environment variables for bot runtime and commented placeholders for CI/CD secrets.
IDE/Editor Ignores
.idea/.gitignore
Ignores IDE workspace files, shelves, and HTTP client requests within .idea/.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Dev as Developer
  participant GH as GitHub
  participant CI as GitHub Actions (CI/CD)
  participant DH as Docker Hub
  participant RS as Remote Server

  Dev->>GH: Push to main / Create tag vX.Y.Z / PR
  GH-->>CI: Trigger workflow

  rect rgba(200,230,255,0.3)
  note over CI: Job: build
  CI->>CI: Build image (linux/amd64) with Buildx --load
  CI->>CI: Runtime check (python -c "import bot")
  CI-->>GH: Build status
  end

  alt Tag push (refs/tags/v*)
    rect rgba(220,255,220,0.3)
    note over CI,DH: Job: release
    CI->>DH: Login and push images (amd64, arm64)<br/>tags: latest, vX.Y.Z
    CI->>RS: SCP docker-compose.yml
    CI->>RS: SSH prepare (mkdir, docker pull)
    end

    rect rgba(255,240,200,0.3)
    note over CI,RS: Job: deploy
    CI->>RS: SSH: docker compose down && up -d
    RS->>RS: docker system prune -f
    end
  else Non-tag events
    CI-->>GH: Release/deploy skipped
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

I thump my paws: a ship to sea!
Docker sails, compose keys free.
Pipelines hum—build, release, deploy—
Carrots packed, no cache to annoy.
Env seeds planted, neat and tight,
To the hub we hop—then moonlit night.
Ship ahop, all green lights bright!

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (10)
.idea/.gitignore (1)

1-5: Scope and placement: consider moving IDE ignores to the repo root and Docker context

Adding an IDE-specific .gitignore under .idea works, but it’s easier to keep things consistent by:

  • Centralizing IDE ignores in the repository’s root .gitignore (or ignoring .idea/ entirely) to avoid accidental commits elsewhere.
  • Ensuring .idea is also listed in .dockerignore so editor artifacts don’t bloat build contexts.

If you intend to keep selected .idea files tracked (e.g., run configs), retain this file and expand the ignore set with other common JetBrains artifacts.

Suggested additions within this file:

 # Default ignored files
 /shelf/
 /workspace.xml
+# Additional IDE-specific files and directories
+/tasks.xml
+/usage.statistics.xml
+/dictionaries/
+/inspectionProfiles/
+/libraries/
 # Editor-based HTTP Client requests
 /httpRequests/
+# Database tools metadata
+/dataSources/
+/dataSources.local.xml

Also verify that .idea/ is excluded in:

  • .gitignore at repo root (unless you intentionally track specific files under .idea)
  • .dockerignore (to reduce build context size)
.dockerignore (2)

85-90: Strengthen secret hygiene: ignore more dotenv and key patterns

Good that .env is ignored. Add common variants to avoid accidental inclusion of secrets.

 .env
+*.env
+.env.*
+.secrets
+.ssh/
+id_rsa
+id_ed25519
+*.pem
+*.key

1-16: Trim build context: ignore VCS and editor configs

To keep images smaller and builds faster, exclude VCS and CI metadata, editor settings, and Compose files that don’t need to be copied into the image.

 .Python
 [Bb]in
 [Ii]nclude
 [Ll]ib
 [Ll]ib64
 [Ll]ocal
 [Ss]cripts
 pyvenv.cfg
 .venv
 pip-selfcheck.json
 .idea
+.vscode
+.DS_Store
+.git
+.gitmodules
+.github/
+docker-compose.yml
.env.example (2)

2-5: Satisfy dotenv-linter ordering (minor) and clarify types

The linter suggests ordering keys; also, make the intended numeric type obvious for ADD_TRACK_TIME_WINDOW.

- BOT_TOKEN=your_telegram_bot_token_here
- DATABASE_NAME=playlist.db
- ADD_TRACK_TIME_WINDOW=60
- LOG_LEVEL=INFO
+ADD_TRACK_TIME_WINDOW=60
+BOT_TOKEN=your_telegram_bot_token_here
+DATABASE_NAME=playlist.db
+LOG_LEVEL=INFO

7-12: Avoid suggesting storing private key content in .env

The workflow expects an SSH key via GitHub Secrets, not committed files. Keep these commented hints, but be explicit that these must be set only as Actions secrets, not .env.

Consider rewording comments to: “Set in GitHub Actions Secrets only; do not store locally or commit.”

docker-compose.yml (3)

8-9: Use the declared named volume or remove the declaration

You declare a named volume data: but mount a bind ./data:/app/data. For predictable persistence across redeploys (especially on a server path like /opt/bilbomusicbot), prefer the named volume.

-    volumes:
-      - ./data:/app/data
+    volumes:
+      - data:/app/data

If you want the bind mount instead, remove the bottom volumes: section to avoid confusion.


6-7: Pass through additional runtime envs (optional)

.env.example lists DATABASE_NAME, ADD_TRACK_TIME_WINDOW, and LOG_LEVEL. If your bot reads these, surface them here for clarity.

     environment:
-      - BOT_TOKEN=${BOT_TOKEN}
+      - BOT_TOKEN=${BOT_TOKEN}
+      - DATABASE_NAME=${DATABASE_NAME:-playlist.db}
+      - ADD_TRACK_TIME_WINDOW=${ADD_TRACK_TIME_WINDOW:-60}
+      - LOG_LEVEL=${LOG_LEVEL:-INFO}

18-18: Add newline at EOF (lint)
[yamllint] flagged missing newline. Add a newline to satisfy linters and tooling.

-  data:
+  data:
+
.github/workflows/ci-cd.yml (2)

49-56: Minor: remove commented key line and add QEMU for multi-arch

Clean up the commented “key” in the Docker login step (it’s unrelated), and add QEMU setup to ensure smooth multi-arch builds.

     - name: Login to Docker Hub
       uses: docker/login-action@v3
       with:
         username: ${{ secrets.DOCKER_USERNAME }}
         password: ${{ secrets.DOCKER_PASSWORD }}
-        # if your server support ssh key use this format
-        # key: ${{ secrets.SERVER_SSH_KEY }}

Add after checkout in both build and release jobs:

+    - name: Set up QEMU
+      uses: docker/setup-qemu-action@v3

1-10: Hardening: add permissions and concurrency; fix lint nits

  • Restrict default token permissions.
  • Prevent overlapping deployments per ref.
  • Address yamllint indentation/trailing-space warnings and ensure newline at EOF.
 name: CI/CD Pipeline

 on:
   push:
     branches: [ main ]
     tags:
       - "v*.*.*"
   pull_request:
     branches: [ main ]
 
+permissions:
+  contents: read
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: false

Also run yamllint and fix reported indentation/trailing spaces, and add a newline at EOF.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between cf5239e and 31a12a5.

📒 Files selected for processing (6)
  • .dockerignore (1 hunks)
  • .env.example (1 hunks)
  • .github/workflows/ci-cd.yml (1 hunks)
  • .idea/.gitignore (1 hunks)
  • Dockerfile (1 hunks)
  • docker-compose.yml (1 hunks)
🧰 Additional context used
🪛 dotenv-linter (3.3.0)
.env.example

[warning] 4-4: [UnorderedKey] The ADD_TRACK_TIME_WINDOW key should go before the BOT_TOKEN key

(UnorderedKey)

🪛 YAMLlint (1.37.1)
docker-compose.yml

[error] 18-18: no new line character at the end of file

(new-line-at-end-of-file)

.github/workflows/ci-cd.yml

[warning] 16-16: wrong indentation: expected 6 but found 4

(indentation)


[error] 18-18: trailing spaces

(trailing-spaces)


[error] 21-21: trailing spaces

(trailing-spaces)


[error] 25-25: trailing spaces

(trailing-spaces)


[warning] 43-43: wrong indentation: expected 6 but found 4

(indentation)


[error] 45-45: trailing spaces

(trailing-spaces)


[error] 48-48: trailing spaces

(trailing-spaces)


[error] 63-63: trailing spaces

(trailing-spaces)


[error] 72-72: trailing spaces

(trailing-spaces)


[error] 83-83: trailing spaces

(trailing-spaces)


[warning] 90-90: wrong indentation: expected 6 but found 4

(indentation)


[error] 102-102: no new line character at the end of file

(new-line-at-end-of-file)

🔇 Additional comments (2)
Dockerfile (2)

1-1: Pin base image more tightly for reproducibility

python:3.13-slim will float to the latest patch, changing over time. Pin to a specific patch or digest to ensure reproducible builds.

If you want, I can propose a pinned digest once you confirm the exact variant (bookworm vs. bullseye) you target.


7-7: Optional: verify 3.13 compatibility of dependencies

Python 3.13 is relatively new; some libs may not have wheels yet. If builds fail or pull in compilers, consider 3.12 as a fallback or install build deps.

I can check your requirements.txt for 3.13 compatibility if you share it.

Comment thread .dockerignore
Comment thread .github/workflows/ci-cd.yml
Comment on lines +64 to +72
- name: Copy docker-compose.yml to server
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
password: ${{ secrets.SERVER_SSH_KEY }}
source: "docker-compose.yml"
target: "/opt/bilbomusicbot/"

@coderabbitai coderabbitai Bot Aug 22, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Incorrect credential field for SCP: use key, not password; ensure target dir exists first

You’re passing an SSH private key via the password field, which won’t work. Also, ensure /opt/bilbomusicbot exists before scp.

Apply:

-    - name: Copy docker-compose.yml to server
-      uses: appleboy/scp-action@v0.1.7
-      with:
-        host: ${{ secrets.SERVER_HOST }}
-        username: ${{ secrets.SERVER_USER }}
-        password: ${{ secrets.SERVER_SSH_KEY }}
-        source: "docker-compose.yml"
-        target: "/opt/bilbomusicbot/"
+    - name: Prepare server for deployment
+      uses: appleboy/ssh-action@v1.0.3
+      with:
+        host: ${{ secrets.SERVER_HOST }}
+        username: ${{ secrets.SERVER_USER }}
+        key: ${{ secrets.SERVER_SSH_KEY }}
+        script: |
+          mkdir -p /opt/bilbomusicbot
+          docker pull ${{ secrets.DOCKER_USERNAME }}/bilbomusicbot:latest
+
+    - name: Copy docker-compose.yml to server
+      uses: appleboy/scp-action@v0.1.7
+      with:
+        host: ${{ secrets.SERVER_HOST }}
+        username: ${{ secrets.SERVER_USER }}
+        key: ${{ secrets.SERVER_SSH_KEY }}
+        source: "docker-compose.yml"
+        target: "/opt/bilbomusicbot/"

Note: This reorders steps so the directory exists before SCP and fixes the credential field.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Copy docker-compose.yml to server
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
password: ${{ secrets.SERVER_SSH_KEY }}
source: "docker-compose.yml"
target: "/opt/bilbomusicbot/"
- name: Prepare server for deployment
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SERVER_SSH_KEY }}
script: |
mkdir -p /opt/bilbomusicbot
docker pull ${{ secrets.DOCKER_USERNAME }}/bilbomusicbot:latest
- name: Copy docker-compose.yml to server
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SERVER_SSH_KEY }}
source: "docker-compose.yml"
target: "/opt/bilbomusicbot/"
🧰 Tools
🪛 YAMLlint (1.37.1)

[error] 72-72: trailing spaces

(trailing-spaces)

🤖 Prompt for AI Agents
In .github/workflows/ci-cd.yml around lines 64 to 72, the scp step incorrectly
supplies the SSH private key via the `password` field and runs before the remote
target directory is created; update the workflow to first ensure
/opt/bilbomusicbot exists on the server (e.g., an SSH or run step that creates
the directory with proper permissions) and then call appleboy/scp-action with
the private key passed in the `key` field (not `password`), keeping source as
"docker-compose.yml" and target "/opt/bilbomusicbot/".

@anvaari anvaari Aug 26, 2025

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Art4Man please use appleboy/scp-action@v1.0.0 and also use key instead of password. Please refer to this doc https://github.com/appleboy/scp-action

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Comment thread .github/workflows/ci-cd.yml
Comment thread .github/workflows/ci-cd.yml
Comment thread Dockerfile
Comment on lines +5 to +11
COPY requirements.txt .

RUN pip install --no-cache-dir -r requirements.txt

COPY . .

CMD ["python", "bot.py"] No newline at end of file

@coderabbitai coderabbitai Bot Aug 22, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Run as non-root and add Python runtime envs

Running as root is risky in production. Also consider standard Python envs to reduce noise and improve logging behavior.

 FROM python:3.13-slim

 WORKDIR /app

+ENV PYTHONDONTWRITEBYTECODE=1 \
+    PYTHONUNBUFFERED=1
+
 COPY requirements.txt .
 
 RUN pip install --no-cache-dir -r requirements.txt
 
 COPY . .
 
-CMD ["python", "bot.py"]
+# Create non-root user and switch
+RUN addgroup --system app && adduser --system --ingroup app app && \
+    chown -R app:app /app
+USER app
+
+CMD ["python", "bot.py"]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "bot.py"]
FROM python:3.13-slim
WORKDIR /app
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
# Create non-root user and switch
RUN addgroup --system app && adduser --system --ingroup app app && \
chown -R app:app /app
USER app
CMD ["python", "bot.py"]

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Art4Man Also please consider this suggestion.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for this great PR @Art4Man .
But we need to set all variables in .env.example as environment variables in the container. I think setting them as secret is a good option

Comment thread .env.example
BOT_TOKEN=your_telegram_bot_token_here
DATABASE_NAME=playlist.db
ADD_TRACK_TIME_WINDOW=60
LOG_LEVEL=INFO

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also we need LOG_FILE here

Comment thread docker-compose.yml
environment:
- BOT_TOKEN=${BOT_TOKEN}
volumes:
- ./data:/app/data

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There isn't data folder. It's better to mount log directory

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants