Skip to content

chore: add docker shell profiles to non interactive ssh sessions#800

Open
raghavyuva wants to merge 1 commit intofeat/disable_deployment_code_persistancefrom
feat/docker_compose_ssh_profile
Open

chore: add docker shell profiles to non interactive ssh sessions#800
raghavyuva wants to merge 1 commit intofeat/disable_deployment_code_persistancefrom
feat/docker_compose_ssh_profile

Conversation

@raghavyuva
Copy link
Owner

@raghavyuva raghavyuva commented Dec 25, 2025

User description

Issue

Link to related issue(s):


Description

Short summary of what this PR changes or introduces.


Scope of Change

Select all applicable areas impacted by this PR:

  • View (UI/UX)
  • API
  • CLI
  • Infra / Deployment
  • Docs
  • Other (specify): ________

Screenshot / Video / GIF (if applicable)

Attach or embed screenshots, screen recordings, or GIFs demonstrating the feature or fix.


Related PRs (if any)

Link any related or dependent PRs across repos.


Additional Notes for Reviewers (optional)

Anything reviewers should know before testing or merging (e.g., environment variables, setup steps).


Developer Checklist

To be completed by the developer who raised the PR.

  • Add valid/relevant title for the PR
  • Self-review done
  • Manual dev testing done
  • No secrets exposed
  • No merge conflicts
  • Docs added/updated (if applicable)
  • Removed debug prints / secrets / sensitive data
  • Unit / Integration tests passing
  • Follows all standards defined in Nixopus Docs

Reviewer Checklist

To be completed by the reviewer before merge.

  • Peer review done
  • No console.logs / fmt.prints left
  • No secrets exposed
  • If any DB migrations, migration changes are verified
  • Verified release changes are production-ready

PR Type

Enhancement, Bug fix


Description

  • Add docker PATH prefix to SSH non-interactive sessions

  • Ensures docker command availability in ComposeUp, ComposeDown, ComposeBuild

  • Exports multiple common docker binary locations for cross-platform compatibility


Diagram Walkthrough

flowchart LR
  A["SSH Non-Interactive Sessions"] -->|missing docker in PATH| B["Docker Commands Fail"]
  C["dockerPathPrefix constant"] -->|prepends PATH exports| D["ComposeUp/Down/Build"]
  D -->|docker command found| E["SSH Commands Execute Successfully"]
Loading

File Walkthrough

Relevant files
Bug fix
init.go
Add docker PATH prefix to compose operations                         

api/internal/features/deploy/docker/init.go

  • Added dockerPathPrefix constant that exports docker binary paths for
    SSH sessions
  • Updated ComposeUp() to prepend dockerPathPrefix to environment
    variables
  • Updated ComposeDown() to prepend dockerPathPrefix to docker compose
    command
  • Updated ComposeBuild() to prepend dockerPathPrefix to environment
    variables
+7/-3     

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 25, 2025

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/docker_compose_ssh_profile

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

Comment @coderabbitai help to get the list of available commands and usage tips.

@qodo-code-review
Copy link

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
Shell command injection

Description: The new command-prefixing continues to build remote SSH commands via string concatenation
(e.g., envVarsStr := dockerPathPrefix and command := fmt.Sprintf("%sdocker compose -f %s
down", dockerPathPrefix, composeFilePath)), so if composeFilePath or any envVars
keys/values can be influenced by an attacker, they could inject additional shell syntax
(e.g., composeFilePath containing "; rm -rf / #), leading to remote command execution.
init.go [334-365]

Referred Code
	envVarsStr := dockerPathPrefix
	for k, v := range envVars {
		envVarsStr += fmt.Sprintf("export %s=%s && ", k, v)
	}
	// Use --force-recreate to handle existing containers and --remove-orphans to clean up old containers
	command := fmt.Sprintf("%sdocker compose -f %s up -d --force-recreate --remove-orphans 2>&1", envVarsStr, composeFilePath)
	output, err := client.RunCommand(command)
	if err != nil {
		return fmt.Errorf("failed to start docker compose services: %v, output: %s", err, output)
	}
	return nil
}

// ComposeDown stops and removes the Docker Compose services
func (s *DockerService) ComposeDown(composeFilePath string) error {
	client := ssh.NewSSH()
	command := fmt.Sprintf("%sdocker compose -f %s down", dockerPathPrefix, composeFilePath)
	output, err := client.RunCommand(command)
	if err != nil {
		return fmt.Errorf("failed to stop docker compose services: %v, output: %s", err, output)
	}


 ... (clipped 11 lines)
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

🔴
Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Command injection risk: User-controlled values (envVars keys/values and composeFilePath) are interpolated into
shell commands without quoting/escaping, enabling shell injection and unsafe PATH/export
manipulation.

Referred Code
	envVarsStr := dockerPathPrefix
	for k, v := range envVars {
		envVarsStr += fmt.Sprintf("export %s=%s && ", k, v)
	}
	// Use --force-recreate to handle existing containers and --remove-orphans to clean up old containers
	command := fmt.Sprintf("%sdocker compose -f %s up -d --force-recreate --remove-orphans 2>&1", envVarsStr, composeFilePath)
	output, err := client.RunCommand(command)
	if err != nil {
		return fmt.Errorf("failed to start docker compose services: %v, output: %s", err, output)
	}
	return nil
}

// ComposeDown stops and removes the Docker Compose services
func (s *DockerService) ComposeDown(composeFilePath string) error {
	client := ssh.NewSSH()
	command := fmt.Sprintf("%sdocker compose -f %s down", dockerPathPrefix, composeFilePath)
	output, err := client.RunCommand(command)
	if err != nil {
		return fmt.Errorf("failed to stop docker compose services: %v, output: %s", err, output)
	}


 ... (clipped 11 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Shell quoting edge-cases: The new shell-prefix approach relies on string-concatenated commands and unquoted
values/paths, which can break on spaces/special characters and lead to hard-to-debug
failures without explicit escaping/validation.

Referred Code
const dockerPathPrefix = "export PATH=$PATH:/usr/local/bin:/usr/bin:/snap/bin:/opt/homebrew/bin && "

// ComposeUp starts the Docker Compose services defined in the specified compose file
func (s *DockerService) ComposeUp(composeFilePath string, envVars map[string]string) error {
	client := ssh.NewSSH()
	envVarsStr := dockerPathPrefix
	for k, v := range envVars {
		envVarsStr += fmt.Sprintf("export %s=%s && ", k, v)
	}
	// Use --force-recreate to handle existing containers and --remove-orphans to clean up old containers
	command := fmt.Sprintf("%sdocker compose -f %s up -d --force-recreate --remove-orphans 2>&1", envVarsStr, composeFilePath)
	output, err := client.RunCommand(command)
	if err != nil {
		return fmt.Errorf("failed to start docker compose services: %v, output: %s", err, output)
	}
	return nil
}

// ComposeDown stops and removes the Docker Compose services
func (s *DockerService) ComposeDown(composeFilePath string) error {
	client := ssh.NewSSH()


 ... (clipped 16 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status:
Error output exposure: The returned errors include raw remote command output, which may expose internal details
(and potentially sensitive values) depending on who receives these errors and where they
are surfaced.

Referred Code
		return fmt.Errorf("failed to start docker compose services: %v, output: %s", err, output)
	}
	return nil
}

// ComposeDown stops and removes the Docker Compose services
func (s *DockerService) ComposeDown(composeFilePath string) error {
	client := ssh.NewSSH()
	command := fmt.Sprintf("%sdocker compose -f %s down", dockerPathPrefix, composeFilePath)
	output, err := client.RunCommand(command)
	if err != nil {
		return fmt.Errorf("failed to stop docker compose services: %v, output: %s", err, output)
	}

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status:
Raw output propagation: The code propagates raw command output in error strings, which can end up in logs upstream
and may unintentionally capture sensitive data from remote execution.

Referred Code
		return fmt.Errorf("failed to start docker compose services: %v, output: %s", err, output)
	}
	return nil
}

// ComposeDown stops and removes the Docker Compose services
func (s *DockerService) ComposeDown(composeFilePath string) error {
	client := ssh.NewSSH()
	command := fmt.Sprintf("%sdocker compose -f %s down", dockerPathPrefix, composeFilePath)
	output, err := client.RunCommand(command)
	if err != nil {
		return fmt.Errorf("failed to stop docker compose services: %v, output: %s", err, output)
	}

Learn more about managing compliance generic rules or creating your own custom rules

Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label
@qodo-code-review
Copy link

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Security
Quote env var values

Use the %q format specifier in fmt.Sprintf to quote and escape environment
variable values, preventing command injection and safely handling special
characters.

api/internal/features/deploy/docker/init.go [334-337]

 envVarsStr := dockerPathPrefix
 for k, v := range envVars {
-    envVarsStr += fmt.Sprintf("export %s=%s && ", k, v)
+    envVarsStr += fmt.Sprintf("export %s=%q && ", k, v)
 }
  • Apply / Chat
Suggestion importance[1-10]: 10

__

Why: This suggestion correctly identifies a critical command injection vulnerability and proposes using the %q format specifier, which is the idiomatic Go way to safely quote strings for shell commands. This effectively mitigates the security risk.

High
General
Dynamically find docker path instead of hardcoding

Instead of hardcoding paths for the Docker executable in dockerPathPrefix,
dynamically locate it using command -v docker for better portability, with a
fallback to the original hardcoded paths.

api/internal/features/deploy/docker/init.go [327-329]

 // dockerPathPrefix returns a shell command prefix that ensures docker is in PATH
 // This is needed because SSH non-interactive sessions don't source shell profiles
-const dockerPathPrefix = "export PATH=$PATH:/usr/local/bin:/usr/bin:/snap/bin:/opt/homebrew/bin && "
+const dockerPathPrefix = "DOCKER_PATH=$(command -v docker) || DOCKER_PATH='' ; if [ -n \"$DOCKER_PATH\" ]; then export PATH=$(dirname \"$DOCKER_PATH\"):$PATH; else export PATH=$PATH:/usr/local/bin:/usr/bin:/snap/bin:/opt/homebrew/bin; fi && "
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly points out that hardcoding paths in dockerPathPrefix is brittle. Using command -v docker to dynamically find the executable path is a more robust and portable solution, improving the reliability of the feature across different environments.

Medium
Capture stderr in commands

Redirect stderr to stdout (by adding 2>&1) for the ComposeDown and ComposeBuild
commands to ensure error messages are captured in the command output.

api/internal/features/deploy/docker/init.go [350-365]

-command := fmt.Sprintf("%sdocker compose -f %s down", dockerPathPrefix, composeFilePath)
+command := fmt.Sprintf("%sdocker compose -f %s down 2>&1", dockerPathPrefix, composeFilePath)
 ...
-command := fmt.Sprintf("%sdocker compose -f %s build", envVarsStr, composeFilePath)
+command := fmt.Sprintf("%sdocker compose -f %s build 2>&1", envVarsStr, composeFilePath)

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 7

__

Why: This suggestion improves error handling by ensuring that stderr from docker compose commands is captured. This is crucial for debugging failed deployments, as error messages would otherwise be lost.

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