Repository: alob-mtc/invok
Pull request #23: Feat: dockerize invok core and some refactors
Author: login: dandax123
Created: 2025-04-27T01:18:15Z | Merged: 2025-05-09T15:34:09Z

closes #17 

https://forums.docker.com/t/mounting-using-var-run-docker-sock-in-a-container-not-running-as-root/34390/11

Ultimately, it's trying to do Docker out of  Docker. 

The actual fix was
a. install the Docker CLI in the image
b. Use `DOCKER_HOST` to connect instead of sockets because socket requires root permission, the container is running as non-root, and I don't want to change the host file permissions. From the link above, the best is to use `socat` to host the socket.


Also added some refactors and `jwt_secret` as part of the server config


Linked issue #17: Dockerize api-controller
Issue:
- App doesn't seem to be able to connect to host docker from within a container even after mounting the host docker socket into the container.
- App is not able to create or run docker image/container 

TODO: 
- Investigate and come up with a fix 

Implication:
- This will open up out of the box deployability and scalability that docker app comes with 
Linked issue comments: user: login: dandax123
created_at: 2025-04-14T11:39:42Z
body: I don't fully follow this issue. 
author_association: CONTRIBUTOR
user: login: alob-mtc
created_at: 2025-04-24T21:54:04Z
body: At the moment if you dockerize serverless-core(api controller) we lose the ability to create and run functions, this is because creating and running functions relies on communication with host docker engine via the docker socket. The issue is getting a dockerized app to communicate with the host docker engine. All the current attempts I’ve tried have failed so far, hence the reason for this issue (you can try replicating this locally) 
author_association: OWNER
user: login: dandax123
created_at: 2025-04-26T17:43:04Z
body: I will look at this now: https://docs.docker.com/engine/network/drivers/host/, I'm not sure, but I think that should fix that

author_association: CONTRIBUTOR

Diff:
diff --git a/.gitignore b/.gitignore
index 2d21261..0767b7e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,8 @@
 # will have compiled files and executables
 debug/
 target/
+hello-world/
+config.json
 
 # These are backup files generated by rustfmt
 **/*.rs.bk
diff --git a/Cargo.lock b/Cargo.lock
index 093be64..81e7d52 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3057,7 +3057,6 @@ dependencies = [
  "axum",
  "db_entities",
  "db_migrations",
- "dotenvy",
  "futures-util",
  "http 0.2.12",
  "hyper 0.14.32",
diff --git a/Dockerfile b/Dockerfile
index c9d30f1..d242149 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -21,18 +21,21 @@ FROM debian:bookworm-slim
 RUN apt-get update && apt-get install -y \
     ca-certificates \
     libssl3 \
+    docker.io \
  && rm -rf /var/lib/apt/lists/*
 
 # Create a non-root user 'appuser' and add them to the 'daemon' group.
 # The 'daemon' group typically has GID 1 on many systems.
 RUN useradd -m -G daemon appuser
 
+# add to user
+RUN groupadd -f docker && usermod -aG docker appuser
+
 # Set working directory
 WORKDIR /app
 
 # Copy the compiled binary from the builder stage
 COPY --from=builder /usr/src/app/target/release/serverless-core /usr/local/bin
-COPY --from=builder /usr/src/app/.env ./
 
 # Ensure the binary is executable
 RUN chmod +x /usr/local/bin/serverless-core
@@ -43,13 +46,5 @@ USER appuser
 # Expose the port your API listens on (adjust if necessary)
 EXPOSE 3000
 
-# ---
-# IMPORTANT:
-# To allow your containerized API to interact with the Docker daemon,
-# mount the host's Docker socket when running the container:
-#
-#   docker run -v /var/run/docker.sock:/var/run/docker.sock -p 3000:3000 your-image-name
-# ---
-
 # Start the API application
 CMD ["serverless-core"]
\ No newline at end of file
diff --git a/Readme.md b/Readme.md
index ab05d40..4c67c4a 100644
--- a/Readme.md
+++ b/Readme.md
@@ -49,6 +49,7 @@ This allows you to create, deploy, and test serverless functions directly in you
 ### Prerequisites
 
 For local development and deployment:
+
 - Docker and Docker Compose
 - Rust toolchain (for building the CLI)
 
@@ -74,26 +75,26 @@ cd cli
 cargo build --release
 
 # Optional: Move the binary to your PATH
-cp target/release/serverless-cli /usr/local/bin
+cp target/release/invok /usr/local/bin
 ```
 
 ### Creating and Deploying Your First Function
 
 ```sh
 # Register a user account
-serverless-cli register --email user@example.com --password your_password
+invok register --email user@example.com --password your_password
 
 # Login
-serverless-cli login --email user@example.com --password your_password
+invok login --email user@example.com --password your_password
 
 # Create a new function (defaults to Go runtime)
-serverless-cli create-function -n hello-world
+invok create -n hello-world
 
 # Deploy your function
-serverless-cli deploy-function -n hello-world
+invok deploy -n hello-world
 
 # List your deployed functions
-serverless-cli list
+invok list
 ```
 
 ## Key Components
@@ -152,12 +153,13 @@ The framework uses JWT-based authentication:
 The framework implements function namespacing to ensure isolation between different users:
 
 1. Each function is associated with a user's UUID in the database
-2. Functions are invoked using the URL pattern: `/functions/{user-uuid}/invoke/{function-name}`
+2. Functions are invoked using the URL pattern: `/invok/{user-uuid}/{function-name}`
 3. A unique database index prevents name collisions within a user's namespace
 4. The system validates that a user can only access and invoke their own functions
 5. Container names include a hash of the user's UUID for better organization
 
 This ensures that:
+
 - Users can create functions with the same names without conflicts
 - One user cannot access or modify another user's functions
 - Function isolation is maintained both in the database and at runtime
@@ -236,6 +238,7 @@ Try out the serverless framework without installing anything using our web-based
 [Invok Playground](https://freeserverless.com/functions/29e2d84f-7a0e-42dd-8b70-a7d7c7c04199/invoke/invok-ide)
 
 The playground allows you to:
+
 - Create serverless functions directly in your browser
 - Deploy functions instantly
 - Test your functions with real-time execution
@@ -243,4 +246,4 @@ The playground allows you to:
 
 Feel free to play around with the host here:
 [serverless-framework-host](https://freeserverless.com)
-deploy/host your own function and see it for yourself (try not to break the host-machine 😂 *it's a small size server for demo only*)
+deploy/host your own function and see it for yourself (try not to break the host-machine 😂 _it's a small size server for demo only_)
diff --git a/build_cli.sh b/build_cli.sh
new file mode 100755
index 0000000..888e451
--- /dev/null
+++ b/build_cli.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+set -eu pipefail
+pushd cli
+cargo build --release
+popd
+
+sudo chmod +x target/release/invok 
+sudo cp target/release/invok /usr/local/bin
diff --git a/cli/src/host_manager.rs b/cli/src/host_manager.rs
index 33a5eff..410365f 100644
--- a/cli/src/host_manager.rs
+++ b/cli/src/host_manager.rs
@@ -4,8 +4,12 @@ This module serves as the base for host management for the CLI.
 */
 
 /// HOST_BASE is the base URL for the API server
+///
+/// TODO: dynamically configure the host
 const HOST_BASE: &str = "https://freeserverless.com";
 
+// const HOST_BASE: &str = "http://localhost:3000";
+
 /// Generates the URL for the login endpoint
 pub fn auth_login_url() -> String {
     format!("{}/auth/login", HOST_BASE)
@@ -16,9 +20,9 @@ pub fn auth_register_url() -> String {
 }
 /// Generates the URL for the function upload endpoint
 pub fn function_upload_url() -> String {
-    format!("{}/functions/upload", HOST_BASE)
+    format!("{}/invok/deploy", HOST_BASE)
 }
 /// Generates the URL for the function list endpoint
 pub fn function_list_url() -> String {
-    format!("{}/functions", HOST_BASE)
+    format!("{}/invok/list", HOST_BASE)
 }
diff --git a/cli/src/serverless_function.rs b/cli/src/serverless_function.rs
index 8bcf17d..077849a 100644
--- a/cli/src/serverless_function.rs
+++ b/cli/src/serverless_function.rs
@@ -168,6 +168,8 @@ pub fn deploy_function(name: &str) -> Result<(), FunctionError> {
     // Reset the cursor to the beginning of the buffer
     dest_zip.set_position(0);
 
+    println!("Zipped up the folder service... '{}'", name);
+
     // Try authenticated deployment first
     deploy_with_auth(name, dest_zip)?;
 
diff --git a/docker-compose.yml b/docker-compose.yml
index 2043ce7..001c491 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,19 +1,51 @@
-version: '3.8'
-
 services:
+  socat:
+    image: alpine/socat
+    user: root
+    command: tcp-listen:2375,fork,reuseaddr unix-connect:/var/run/docker.sock
+    restart: unless-stopped
+    networks:
+      - infra_network
+    volumes:
+      - type: bind
+        source: /var/run/docker.sock
+        target: /var/run/docker.sock
+
+  invok_core:
+    container_name: invok-core
+    build:
+      context: .
+      cache_from: []
+    depends_on:
+      - postgres
+      - redis
+      - socat
+    ports:
+      - "3000:3000"
+    networks:
+      - infra_network
+    environment:
+      DATABASE_URL: "postgres://postgres:postgres@postgres:5432/serverless"
+      REDIS_URL: "redis://redis:6379"
+      AUTH_JWT_SECRET: "your-secret-key-here"
+      RUST_LOG: "debug"
+      DOCKER_HOST: "socat:2375"
+      # It is important to set this correctly, you can find the correct value by running `docker network ls` and it should be <>_infra_network
+      DOCKER_COMPOSE_NETWORK: "serverless-framework_infra_network"
+
   postgres:
     image: postgres:15
     container_name: postgres
     environment:
-      POSTGRES_USER: root
-      POSTGRES_PASSWORD: root
-      POSTGRES_DB: serverless
+      - POSTGRES_USER=postgres
+      - POSTGRES_PASSWORD=postgres
+      - POSTGRES_DB=serverless
     ports:
       - "5432:5432"
     volumes:
       - postgres_data:/var/lib/postgresql/data
     networks:
-      - backend
+      - infra_network
 
   redis:
     image: redis:7
@@ -21,11 +53,11 @@ services:
     ports:
       - "6379:6379"
     networks:
-      - backend
+      - infra_network
 
 volumes:
   postgres_data:
 
 networks:
-  backend:
+  infra_network:
     driver: bridge
diff --git a/runtime/src/core/provisioning.rs b/runtime/src/core/provisioning.rs
index 1019740..7f8bc65 100644
--- a/runtime/src/core/provisioning.rs
+++ b/runtime/src/core/provisioning.rs
@@ -60,7 +60,7 @@ pub async fn provisioning(
     runner_type: &str,
     dockerfile_content: &str,
 ) -> AppResult<()> {
-    let docker = Docker::connect_with_socket_defaults()
+    let docker = Docker::connect_with_http_defaults()
         .map_err(|e| RuntimeError::System(format!("Unable to connect to Docker: {e}")))?;
 
     // Create the build context as a tar archive (in memory).
diff --git a/runtime/src/core/runner.rs b/runtime/src/core/runner.rs
index a16257f..5477226 100644
--- a/runtime/src/core/runner.rs
+++ b/runtime/src/core/runner.rs
@@ -1,18 +1,28 @@
 use crate::shared::error::{AppResult, RuntimeError};
 use bollard::container::{
-    AttachContainerOptions, AttachContainerResults, Config, RemoveContainerOptions,
+    AttachContainerOptions, AttachContainerResults, Config, CreateContainerOptions,
+    NetworkingConfig, RemoveContainerOptions,
 };
 use bollard::models::{HostConfig, PortBinding, PortMap};
+use bollard::network::ConnectNetworkOptions;
 use bollard::Docker;
 use futures_util::StreamExt;
+use std::collections::HashMap;
 use std::sync::mpsc;
 use std::time::{Duration, Instant};
 use tokio::spawn;
 
-const TIMEOUT_DEFAULT: u64 = 5;
 const BYTES_IN_MB: i64 = 1024 * 1024; // 1 MB in bytes
 const SIZE_256_MB: i64 = 256 * BYTES_IN_MB; // 256 MB in bytes
 const NUM_CPUS: f64 = 1.0;
+#[derive(Debug, Clone)]
+pub struct ContainerDetails {
+    pub container_port: u32,
+    pub bind_port: String,
+    pub container_name: String,
+    pub timeout: u64,
+    pub docker_compose_network_host: String,
+}
 
 /// Spawns a Docker container with given image and ports, attaches to it,
 /// and sets up a timeout/cleanup mechanism.
@@ -20,6 +30,8 @@ const NUM_CPUS: f64 = 1.0;
 /// # Arguments
 ///
 /// * `image_name` - Name of the Docker image to run.
+/// * `container_details` - Details of the Docker container to run.
+///
 /// * `port_binding` - Port mapping string of the form "HOST_PORT:CONTAINER_PORT".
 /// * `timeout` - Optional duration after which to trigger a timeout. Defaults to 5s.
 ///
@@ -27,38 +39,27 @@ const NUM_CPUS: f64 = 1.0;
 ///
 /// * On success, returns the container ID as a `String`.
 /// * On error, returns an `AppError`.
-pub async fn runner(
-    image_name: &str,
-    port_binding: &str,
-    timeout: Option<Duration>,
-) -> AppResult<String> {
+///
+pub async fn runner(image_name: &str, container_details: ContainerDetails) -> AppResult<()> {
     // Connect to Docker via Unix socket (or named pipe on Windows).
-    let docker = Docker::connect_with_socket_defaults()
+    let docker = Docker::connect_with_http_defaults()
         .map_err(|e| RuntimeError::System(format!("Failed to connect to Docker: {e}")))?;
 
     let start_time = Instant::now();
 
-    // Safely parse the port mapping: "HOST_PORT:CONTAINER_PORT".
-    let ports: Vec<&str> = port_binding.split(':').collect();
-    if ports.len() != 2 {
-        return Err(RuntimeError::System(
-            "Invalid port binding format; expected 'HOST_PORT:CONTAINER_PORT'.".to_string(),
-        ));
-    }
-
-    let host_port = ports[0];
-    let container_port = ports[1];
-
     // Set up port bindings.
     let mut port_map = PortMap::new();
     port_map.insert(
-        format!("{container_port}/tcp"),
+        format!("{}/tcp", container_details.container_port),
         Some(vec![PortBinding {
-            host_ip: Some("127.0.0.1".to_string()),
-            host_port: Some(host_port.to_string()),
+            host_ip: Some("".to_string()),
+            host_port: Some(container_details.bind_port),
         }]),
     );
 
+    let mut exposed_ports = HashMap::new();
+    exposed_ports.insert("8080/tcp", HashMap::new());
+
     let (cpu_period, cpu_quota) = cpu_limits(NUM_CPUS);
     // Configure the container.
     let container_config = Config {
@@ -66,6 +67,7 @@ pub async fn runner(
         tty: Some(true),
         attach_stdout: Some(true),
         attach_stderr: Some(true),
+        exposed_ports: Some(exposed_ports),
         host_config: Some(HostConfig {
             memory: Some(SIZE_256_MB),
             cpu_period: Some(cpu_period),
@@ -79,11 +81,35 @@ pub async fn runner(
 
     // Create the container.
     let create_response = docker
-        .create_container::<&str, &str>(None, container_config)
+        .create_container::<&str, &str>(
+            Some(CreateContainerOptions {
+                name: &container_details.container_name,
+                platform: None,
+            }),
+            container_config,
+        )
         .await
         .map_err(|e| RuntimeError::System(format!("Failed to create container: {e}")))?;
     let container_id = create_response.id.clone();
 
+    // connect it to the network (inner compose network)
+    let network_options = ConnectNetworkOptions {
+        container: container_id.clone(),
+        ..Default::default()
+    };
+
+    docker
+        .connect_network(
+            &container_details.docker_compose_network_host,
+            network_options,
+        )
+        .await
+        .map_err(|e| {
+            RuntimeError::System(format!(
+                "Failed to connect the container to the docker compose network: {e}"
+            ))
+        })?;
+
     // Start the container.
     docker
         .start_container::<String>(&container_id, None)
@@ -117,7 +143,7 @@ pub async fn runner(
     let docker_clone = docker.clone();
     let container_id_clone = container_id.clone();
     spawn(async move {
-        let timeout_val = timeout.unwrap_or_else(|| Duration::from_secs(TIMEOUT_DEFAULT));
+        let timeout_val = Duration::from_secs(container_details.timeout);
 
         // Create a channel-based timeout; trigger_timeout() starts the countdown.
         let (rx, trigger_timeout) = crate::shared::utils::timeout(timeout_val);
@@ -135,7 +161,7 @@ pub async fn runner(
         }
     });
 
-    Ok(container_id)
+    Ok(())
 }
 
 /// Monitors the container process using a timeout channel.
@@ -230,6 +256,16 @@ mod tests {
 #[tokio::test]
 async fn test_runner() {
     // Make sure the "hello-world" image is available locally or can be pulled.
-    let result = runner("test-runner", "8080:8080", None).await;
+    let result = runner(
+        "test-runner",
+        ContainerDetails {
+            container_port: 8080,
+            bind_port: 8080.to_string(),
+            container_name: "c-test".to_string(),
+            timeout: 50,
+            docker_compose_network_host: "asdf".to_string(),
+        },
+    )
+    .await;
     assert!(result.is_ok(), "Container should start successfully.");
 }
diff --git a/serverless_core/Cargo.toml b/serverless_core/Cargo.toml
index 9fc9057..52e9ba1 100644
--- a/serverless_core/Cargo.toml
+++ b/serverless_core/Cargo.toml
@@ -18,7 +18,6 @@ argon2 = "0.5"
 rand_core = { version = "0.6", features = ["std"] }
 axum = { version = "0.6.20", features = ["macros", "multipart"] }
 futures-util = "0.3.30"
-dotenvy = "0.15.7"
 tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
 thiserror = "1.0"
 hyper = { version = "0.14", features = ["server"] }
diff --git a/serverless_core/src/api_controller/config.rs b/serverless_core/src/api_controller/config.rs
index 249bd83..a31cc78 100644
--- a/serverless_core/src/api_controller/config.rs
+++ b/serverless_core/src/api_controller/config.rs
@@ -1,120 +1,44 @@
 use std::env;
+
+use function::InvokFunctionConfig;
+use server::InvokServerConfig;
 use thiserror::Error;
 
+mod function;
+mod server;
+
 /// Error that can occur during configuration loading
 #[derive(Debug, Error)]
-pub enum ConfigError {
+pub enum InvokConfigError {
     #[error("Missing environment variable: {0}")]
     MissingVar(String),
 
-    #[error("Invalid environment variable value: {0}")]
-    InvalidValue(String),
+    #[error("The port {0} provided is invalid")]
+    InvalidPort(String),
 
     #[error("Environment error: {0}")]
     EnvError(#[from] env::VarError),
 }
 
-/// Server configuration
-#[derive(Debug, Clone)]
-pub struct ServerConfig {
-    /// Redis connection URL
-    pub redis_url: String,
-
-    /// Database connection URL
-    pub database_url: String,
-
-    /// Server listen address
-    pub host: String,
-
-    /// Server listen port
-    pub port: u16,
-}
-
-impl ServerConfig {
-    /// Default port to use if not configured
-    pub const DEFAULT_PORT: u16 = 3000;
-
-    /// Default host to bind to if not configured
-    pub const DEFAULT_HOST: &'static str = "0.0.0.0";
-
-    /// Load configuration from environment variables
-    pub fn from_env() -> Result<Self, ConfigError> {
-        // Required variables
-        let redis_url =
-            env::var("REDIS_URL").map_err(|_| ConfigError::MissingVar("REDIS_URL".to_string()))?;
-
-        let database_url = env::var("DATABASE_URL")
-            .map_err(|_| ConfigError::MissingVar("DATABASE_URL".to_string()))?;
-
-        // Optional variables with defaults
-        let host = env::var("SERVER_HOST").unwrap_or_else(|_| Self::DEFAULT_HOST.to_string());
-
-        let port = match env::var("SERVER_PORT") {
-            Ok(port_str) => port_str
-                .parse::<u16>()
-                .map_err(|_| ConfigError::InvalidValue("SERVER_PORT".to_string()))?,
-            Err(_) => Self::DEFAULT_PORT,
-        };
-
-        Ok(Self {
-            redis_url,
-            database_url,
-            host,
-            port,
-        })
-    }
-}
-
-/// Function service configuration
-#[derive(Debug, Clone)]
-pub struct FunctionConfig {
-    /// Default runtime to use for functions
-    pub default_runtime: String,
-
-    /// Maximum function size in bytes
-    pub max_function_size: usize,
-}
-
-impl FunctionConfig {
-    /// Default runtime if not specified
-    pub const DEFAULT_RUNTIME: &'static str = "go";
-
-    /// Default maximum function size (10MB)
-    pub const DEFAULT_MAX_FUNCTION_SIZE: usize = 10 * 1024 * 1024;
-
-    /// Load function configuration from environment
-    pub fn from_env() -> Self {
-        let default_runtime =
-            env::var("DEFAULT_RUNTIME").unwrap_or_else(|_| Self::DEFAULT_RUNTIME.to_string());
-
-        let max_function_size = env::var("MAX_FUNCTION_SIZE")
-            .ok()
-            .and_then(|s| s.parse::<usize>().ok())
-            .unwrap_or(Self::DEFAULT_MAX_FUNCTION_SIZE);
-
-        Self {
-            default_runtime,
-            max_function_size,
-        }
-    }
-}
-
 /// Complete application configuration
 #[derive(Debug, Clone)]
-pub struct AppConfig {
+pub struct InvokConfig {
     /// Server configuration
-    pub server: ServerConfig,
+    pub server_config: InvokServerConfig,
 
     /// Function configuration
-    pub function: FunctionConfig,
+    pub function_config: InvokFunctionConfig,
 }
 
-impl AppConfig {
+impl InvokConfig {
     /// Load complete configuration from environment
-    pub fn load() -> Result<Self, ConfigError> {
-        let server = ServerConfig::from_env()?;
-        let function = FunctionConfig::from_env();
+    pub fn load() -> Result<Self, InvokConfigError> {
+        let server_config = InvokServerConfig::from_env()?;
+        let function_config = InvokFunctionConfig::from_env();
 
-        Ok(Self { server, function })
+        Ok(Self {
+            server_config,
+            function_config,
+        })
     }
 }
diff --git a/serverless_core/src/api_controller/config/function.rs b/serverless_core/src/api_controller/config/function.rs
new file mode 100644
index 0000000..0f1aded
--- /dev/null
+++ b/serverless_core/src/api_controller/config/function.rs
@@ -0,0 +1,38 @@
+use std::env;
+
+const MAX_FUNCTION_SIZE_ENV_VARIABLE: &str = "MAX_FUNCTION_SIZE";
+const DEFAULT_RUNTIME_ENV_VARIABLE: &str = "DEFAULT_RUNTIME";
+
+/// Default runtime if not specified
+pub const DEFAULT_RUNTIME_VALUE: &str = "go";
+
+/// Default maximum function size (10MB)
+pub const DEFAULT_MAX_FUNCTION_SIZE_VALUE: usize = 10 * 1024 * 1024;
+
+/// Function service configuration
+#[derive(Debug, Clone)]
+pub struct InvokFunctionConfig {
+    /// Default runtime to use for functions
+    pub default_runtime: String,
+
+    /// Maximum function size in bytes
+    pub max_function_size: usize,
+}
+
+impl InvokFunctionConfig {
+    /// Load function configuration from environment
+    pub fn from_env() -> Self {
+        let default_runtime = env::var(DEFAULT_RUNTIME_ENV_VARIABLE)
+            .unwrap_or_else(|_| DEFAULT_RUNTIME_VALUE.to_string());
+
+        let max_function_size = env::var(MAX_FUNCTION_SIZE_ENV_VARIABLE)
+            .ok()
+            .and_then(|s| s.parse::<usize>().ok())
+            .unwrap_or(DEFAULT_MAX_FUNCTION_SIZE_VALUE);
+
+        Self {
+            default_runtime,
+            max_function_size,
+        }
+    }
+}
diff --git a/serverless_core/src/api_controller/config/server.rs b/serverless_core/src/api_controller/config/server.rs
new file mode 100644
index 0000000..d9c8170
--- /dev/null
+++ b/serverless_core/src/api_controller/config/server.rs
@@ -0,0 +1,82 @@
+use super::InvokConfigError;
+use std::env;
+
+// Env variables
+const REDIS_URL_ENV_VARIABLE: &str = "REDIS_URL";
+const DATABASE_URL_ENV_VARIABLE: &str = "DATABASE_URL";
+const PORT_ENV_VARIABLE: &str = "SERVER_PORT";
+const SERVER_HOST_ENV_VARIABLE: &str = "SERVER_HOST";
+const AUTH_JWT_SECRET_ENV_VARIABLE: &str = "AUTH_JWT_SECRET";
+
+const DOCKER_COMPOSE_NETWORK_ENV_VARIABLE: &str = "DOCKER_COMPOSE_NETWORK";
+const DOCKER_HOST_ENV_VARIABLE: &str = "DOCKER_HOST";
+
+/// Default port to use if not configured
+const DEFAULT_PORT_VALUE: u16 = 3000;
+
+/// Default host to bind to if not configured
+const DEFAULT_HOST_VALUE: &'static str = "0.0.0.0";
+
+/// Server configuration
+#[derive(Debug, Clone)]
+pub struct InvokServerConfig {
+    /// Redis connection URL
+    pub redis_url: String,
+
+    /// Database connection URL
+    pub database_url: String,
+
+    /// JWT auth secret
+    pub jwt_auth_secret: String,
+
+    /// Server listen address
+    pub host: String,
+
+    /// Docker network  address
+    pub docker_compose_network_host: String,
+
+    /// Server listen port
+    pub port: u16,
+}
+
+impl InvokServerConfig {
+    /// Load configuration from environment variables
+    pub fn from_env() -> Result<Self, InvokConfigError> {
+        // Required variables
+        let redis_url = env::var(REDIS_URL_ENV_VARIABLE)
+            .map_err(|_| InvokConfigError::MissingVar(REDIS_URL_ENV_VARIABLE.to_string()))?;
+
+        let database_url = env::var(DATABASE_URL_ENV_VARIABLE)
+            .map_err(|_| InvokConfigError::MissingVar(DATABASE_URL_ENV_VARIABLE.to_string()))?;
+
+        env::var(DOCKER_HOST_ENV_VARIABLE)
+            .map_err(|_| InvokConfigError::MissingVar(DOCKER_HOST_ENV_VARIABLE.to_string()))?;
+
+        let docker_compose_network_host =
+            env::var(DOCKER_COMPOSE_NETWORK_ENV_VARIABLE).map_err(|_| {
+                InvokConfigError::MissingVar(DOCKER_COMPOSE_NETWORK_ENV_VARIABLE.to_string())
+            })?;
+
+        let jwt_auth_secret = env::var(AUTH_JWT_SECRET_ENV_VARIABLE)
+            .map_err(|_| InvokConfigError::MissingVar(AUTH_JWT_SECRET_ENV_VARIABLE.to_string()))?;
+
+        let host =
+            env::var(SERVER_HOST_ENV_VARIABLE).unwrap_or_else(|_| DEFAULT_HOST_VALUE.to_string());
+
+        let port = match env::var(PORT_ENV_VARIABLE) {
+            Ok(port_str) => port_str
+                .parse::<u16>()
+                .map_err(|_| InvokConfigError::InvalidPort(port_str))?,
+            Err(_) => DEFAULT_PORT_VALUE,
+        };
+
+        Ok(Self {
+            redis_url,
+            database_url,
+            jwt_auth_secret,
+            docker_compose_network_host,
+            host,
+            port,
+        })
+    }
+}
diff --git a/serverless_core/src/api_controller/handlers/auth.rs b/serverless_core/src/api_controller/handlers/auth.rs
index 18b6ff9..f727fc9 100644
--- a/serverless_core/src/api_controller/handlers/auth.rs
+++ b/serverless_core/src/api_controller/handlers/auth.rs
@@ -12,8 +12,6 @@ use uuid::Uuid;
 use crate::api_controller::AppState;
 use crate::db::auth::AuthDBRepo;
 
-// JWT secret key - in production, this should be loaded from an environment variable
-const JWT_SECRET: &[u8] = b"your-secret-key-here";
 // JWT token validity period in seconds (24 hours)
 const TOKEN_VALIDITY: u64 = 24 * 60 * 60;
 
@@ -86,7 +84,10 @@ pub async fn register(
             info!("User registered: {}", user.email);
 
             // Generate a token for the user
-            match generate_token(&user.uuid.to_string()) {
+            match generate_token(
+                &user.uuid.to_string(),
+                &state.config.server_config.jwt_auth_secret,
+            ) {
                 Ok(token) => {
                     let user_response = UserResponse {
                         uuid: user.uuid.to_string(),
@@ -145,7 +146,10 @@ pub async fn login(
             info!("User logged in: {}", user.email);
 
             // Generate a token for the user
-            match generate_token(&user.uuid.to_string()) {
+            match generate_token(
+                &user.uuid.to_string(),
+                &state.config.server_config.jwt_auth_secret,
+            ) {
                 Ok(token) => {
                     let user_response = UserResponse {
                         uuid: user.uuid.to_string(),
@@ -195,11 +199,14 @@ pub async fn login(
 }
 
 /// Validates a JWT token
-pub fn validate_token(token: &str) -> Result<Uuid, jsonwebtoken::errors::Error> {
+pub fn validate_token(
+    token: &str,
+    auth_jwt_secret: &str,
+) -> Result<Uuid, jsonwebtoken::errors::Error> {
     // Decode and validate the token
     let token_data = decode::<Claims>(
         token,
-        &DecodingKey::from_secret(JWT_SECRET),
+        &DecodingKey::from_secret(auth_jwt_secret.as_bytes()),
         &Validation::default(),
     )?;
 
@@ -211,7 +218,10 @@ pub fn validate_token(token: &str) -> Result<Uuid, jsonwebtoken::errors::Error>
 }
 
 /// Generates a JWT token for a user
-fn generate_token(user_uuid: &str) -> Result<String, jsonwebtoken::errors::Error> {
+fn generate_token(
+    user_uuid: &str,
+    auth_jwt_secret: &str,
+) -> Result<String, jsonwebtoken::errors::Error> {
     let now = SystemTime::now()
         .duration_since(UNIX_EPOCH)
         .unwrap()
@@ -226,6 +236,6 @@ fn generate_token(user_uuid: &str) -> Result<String, jsonwebtoken::errors::Error
     encode(
         &Header::default(),
         &claims,
-        &EncodingKey::from_secret(JWT_SECRET),
+        &EncodingKey::from_secret(auth_jwt_secret.as_bytes()),
     )
 }
diff --git a/serverless_core/src/api_controller/handlers/functions.rs b/serverless_core/src/api_controller/handlers/functions.rs
index 53fb358..ecb079c 100644
--- a/serverless_core/src/api_controller/handlers/functions.rs
+++ b/serverless_core/src/api_controller/handlers/functions.rs
@@ -7,13 +7,12 @@ use crate::api_controller::middlewares::jwt::AuthenticatedUser;
 use crate::api_controller::AppState;
 use crate::db::function::FunctionDBRepo;
 use crate::db::models::DeployableFunction;
-use crate::lifecycle_manager::lib::deploy::deploy_function;
-use crate::lifecycle_manager::lib::invoke::{check_function_status, start_function};
+use crate::lifecycle_manager::deploy::deploy_function;
+use crate::lifecycle_manager::invoke::{check_function_status, start_function};
 use crate::utils::utils::make_request;
 use futures_util::stream::StreamExt;
 use std::collections::HashMap;
 use tracing::{error, info};
-use uuid::Uuid;
 
 /// Handles uploading a function as a ZIP file with authentication.
 ///
@@ -29,8 +28,8 @@ pub(crate) async fn upload_function(
 ) -> impl IntoResponse {
     // Get configuration from state
     let supported_archive_ext = ".zip"; // Currently we only support ZIP
-    let default_runtime = &state.config.function.default_runtime;
-    let max_size = state.config.function.max_function_size;
+    let default_runtime = &state.config.function_config.default_runtime;
+    let max_size = state.config.function_config.max_function_size;
 
     // Iterate over the fields in the multipart request.
     while let Ok(Some(mut field)) = multipart.next_field().await {
@@ -199,8 +198,21 @@ pub(crate) async fn call_function(
         return e.into_response();
     }
 
+    let docker_compose_network_host = state
+        .config
+        .server_config
+        .docker_compose_network_host
+        .clone();
+
     // Attempt to start the function using the cache connection.
-    let addr = match start_function(&mut state.cache_conn, &function_name, user_uuid).await {
+    let addr = match start_function(
+        &mut state.cache_conn,
+        &function_name,
+        user_uuid,
+        docker_compose_network_host,
+    )
+    .await
+    {
         Ok(addr) => addr,
         Err(e) => {
             error!(
diff --git a/serverless_core/src/api_controller/middlewares/jwt.rs b/serverless_core/src/api_controller/middlewares/jwt.rs
index 5020bd7..b57e34d 100644
--- a/serverless_core/src/api_controller/middlewares/jwt.rs
+++ b/serverless_core/src/api_controller/middlewares/jwt.rs
@@ -57,21 +57,22 @@ where
                 "Invalid authorization header format".to_string(),
             ));
         }
+        let app_state = AppState::from_ref(state);
 
         // Extract the token
         let token = &auth_header[7..];
 
         // Validate the token
-        let user_uuid = validate_token(token).map_err(|e| {
-            error!("Token validation error: {}", e);
-            AuthError(
-                StatusCode::UNAUTHORIZED,
-                "Invalid or expired token".to_string(),
-            )
-        })?;
+        let user_uuid = validate_token(token, &app_state.config.server_config.jwt_auth_secret)
+            .map_err(|e| {
+                error!("Token validation error: {}", e);
+                AuthError(
+                    StatusCode::UNAUTHORIZED,
+                    "Invalid or expired token".to_string(),
+                )
+            })?;
 
         // Get the app state
-        let app_state = AppState::from_ref(state);
 
         // Verify the user exists in the database
         match AuthDBRepo::find_by_uuid(&app_state.db_conn, user_uuid).await {
diff --git a/serverless_core/src/api_controller/mod.rs b/serverless_core/src/api_controller/mod.rs
index 291bbef..440ebd7 100644
--- a/serverless_core/src/api_controller/mod.rs
+++ b/serverless_core/src/api_controller/mod.rs
@@ -7,13 +7,12 @@ use axum::{
     routing::{any, get, post},
     Router,
 };
-use config::{AppConfig, ConfigError};
+use config::{InvokConfig, InvokConfigError};
 use db_migrations::{Migrator, MigratorTrait};
 use handlers::{
     auth::{login, register},
     functions::{call_function, list_functions, upload_function},
 };
-use hyper::http;
 use redis::aio::MultiplexedConnection;
 use sea_orm::{Database, DatabaseConnection};
 use std::net::SocketAddr;
@@ -28,14 +27,14 @@ pub struct AppState {
     /// Redis connection for caching.
     pub cache_conn: MultiplexedConnection,
     /// Application configuration
-    pub config: AppConfig,
+    pub config: InvokConfig,
 }
 
 /// Custom error type for server initialization.
 #[derive(Debug, Error)]
-pub enum ServerError {
+pub enum InvokAppError {
     #[error("Configuration error: {0}")]
-    ConfigError(#[from] ConfigError),
+    ConfigError(#[from] InvokConfigError),
 
     #[error("Redis connection error: {0}")]
     RedisError(#[from] redis::RedisError),
@@ -46,9 +45,6 @@ pub enum ServerError {
     #[error("Server error: {0}")]
     ServerError(#[from] std::io::Error),
 
-    #[error("Environment loading error: {0}")]
-    EnvError(#[from] dotenvy::Error),
-
     #[error("HTTP server error: {0}")]
     HttpError(#[from] hyper::Error),
 }
@@ -56,27 +52,24 @@ pub enum ServerError {
 /// Starts the server and sets up the necessary connections and routes.
 ///
 /// This function performs the following:
-/// - Loads environment variables from a `.env` file.
 /// - Initializes structured logging.
 /// - Loads application configuration
 /// - Connects to Redis and the database.
 /// - Runs database migrations.
 /// - Sets up the Axum router with defined routes.
 /// - Binds the server to a socket address and starts serving requests.
-pub async fn start_server() -> Result<(), ServerError> {
-    // Load environment variables from .env file
-    dotenvy::dotenv()?;
+pub async fn start_server() -> Result<(), InvokAppError> {
     tracing_subscriber::fmt::init();
 
     // Load application configuration
-    let config = AppConfig::load()?;
+    let config = InvokConfig::load()?;
 
     // Connect to Redis.
-    let client = redis::Client::open(config.server.redis_url.clone())?;
+    let client = redis::Client::open(config.server_config.redis_url.clone())?;
     let cache_conn = client.get_multiplexed_async_connection().await?;
 
     // Connect to the database.
-    let db_conn = Database::connect(config.server.database_url.clone()).await?;
+    let db_conn = Database::connect(config.server_config.database_url.clone()).await?;
 
     // Run database migrations.
     Migrator::up(&db_conn, None).await?;
@@ -93,23 +86,20 @@ pub async fn start_server() -> Result<(), ServerError> {
         .route("/auth/register", post(register))
         .route("/auth/login", post(login))
         // Function management routes
-        .route("/functions", get(list_functions))
-        .route("/functions/upload", post(upload_function))
+        .route("/invok/list", get(list_functions))
+        .route("/invok/deploy", post(upload_function))
         // Function invocation routes
-        .route(
-            "/functions/:namespace/invoke/:function_name",
-            any(call_function),
-        )
+        .route("/invok/:namespace/:function_name", any(call_function))
         .with_state(app_state);
 
     // Build socket address from configuration
     let addr = SocketAddr::new(
         config
-            .server
+            .server_config
             .host
             .parse()
             .unwrap_or_else(|_| "0.0.0.0".parse().unwrap()),
-        config.server.port,
+        config.server_config.port,
     );
 
     info!("Server listening on {}", addr);
diff --git a/serverless_core/src/lifecycle_manager.rs b/serverless_core/src/lifecycle_manager.rs
index 4934171..de500cf 100644
--- a/serverless_core/src/lifecycle_manager.rs
+++ b/serverless_core/src/lifecycle_manager.rs
@@ -1 +1,3 @@
-pub(crate) mod lib;
+pub(crate) mod deploy;
+mod error;
+pub(crate) mod invoke;
diff --git a/serverless_core/src/lifecycle_manager/lib/deploy.rs b/serverless_core/src/lifecycle_manager/deploy.rs
similarity index 98%
rename from serverless_core/src/lifecycle_manager/lib/deploy.rs
rename to serverless_core/src/lifecycle_manager/deploy.rs
index aa2ee8b..2bbd09e 100644
--- a/serverless_core/src/lifecycle_manager/lib/deploy.rs
+++ b/serverless_core/src/lifecycle_manager/deploy.rs
@@ -11,7 +11,7 @@ use tracing::{error, info};
 
 use crate::db::function::FunctionDBRepo;
 use crate::db::models::{DeployableFunction, DeployableFunctionConfig};
-use crate::lifecycle_manager::lib::error::{ServelessCoreError, ServelessCoreResult};
+use crate::lifecycle_manager::error::{ServelessCoreError, ServelessCoreResult};
 use crate::utils::utils::{create_fn_files_base, envs_to_string, generate_hash};
 
 /// Creates a function file structure and extracts its configuration.
diff --git a/serverless_core/src/lifecycle_manager/lib/error.rs b/serverless_core/src/lifecycle_manager/error.rs
similarity index 100%
rename from serverless_core/src/lifecycle_manager/lib/error.rs
rename to serverless_core/src/lifecycle_manager/error.rs
diff --git a/serverless_core/src/lifecycle_manager/lib/invoke.rs b/serverless_core/src/lifecycle_manager/invoke.rs
similarity index 71%
rename from serverless_core/src/lifecycle_manager/lib/invoke.rs
rename to serverless_core/src/lifecycle_manager/invoke.rs
index e94319a..c44c55f 100644
--- a/serverless_core/src/lifecycle_manager/lib/invoke.rs
+++ b/serverless_core/src/lifecycle_manager/invoke.rs
@@ -1,15 +1,13 @@
 use crate::db::cache::FunctionCacheRepo;
 use crate::db::function::FunctionDBRepo;
-use crate::lifecycle_manager::lib::error::{ServelessCoreError, ServelessCoreResult};
-use crate::utils::utils::{generate_hash, random_port};
-use md5;
+use crate::lifecycle_manager::error::{ServelessCoreError, ServelessCoreResult};
+use crate::utils::utils::{generate_hash, random_container_name, random_port};
 use redis::aio::MultiplexedConnection;
-use runtime::core::runner::runner;
+use runtime::core::runner::{runner, ContainerDetails};
 use sea_orm::DatabaseConnection;
-use std::time::Duration;
 use tracing::{error, info};
 use uuid::Uuid;
-
+const TIMEOUT_DEFAULT_IN_SECONDS: u64 = 50;
 /// Checks if a function is registered in the database.
 ///
 /// Returns `Ok(())` if the function exists; otherwise, returns an error
@@ -57,6 +55,7 @@ pub async fn start_function(
     cache_conn: &mut MultiplexedConnection,
     name: &str,
     user_uuid: Uuid,
+    docker_compose_network_host: String,
 ) -> ServelessCoreResult<String> {
     // Generate a shorter hash of the UUID for better container names
     let uuid_short = generate_hash(user_uuid);
@@ -64,6 +63,15 @@ pub async fn start_function(
     // Create a unique function name based on function name and user's UUID hash
     let function_key = format!("{name}-{uuid_short}");
 
+    // Generate a random port and prepare the service address.
+    let container_details = ContainerDetails {
+        container_port: 8080,
+        bind_port: random_port(),
+        container_name: random_container_name(),
+        timeout: TIMEOUT_DEFAULT_IN_SECONDS,
+        docker_compose_network_host,
+    };
+
     // Check if the function is already running.
     if let Some(addr) = FunctionCacheRepo::get_function(cache_conn, &function_key).await {
         info!(
@@ -73,35 +81,32 @@ pub async fn start_function(
         return Ok(addr);
     }
 
-    // Generate a random port and prepare the service address.
-    let port = random_port();
-    let addr = format!("localhost:{}", port);
-    let timeout = 10;
-
     // Attempt to run the function container with a timeout slightly longer than the cache TTL.
-    match runner(
-        &function_key,
-        &format!("{port}:8080"),
-        Some(Duration::from_secs(timeout + 2)),
-    )
-    .await
-    {
-        Err(e) => {
+    runner(&function_key, container_details.clone())
+        .await
+        .map_err(|e| {
             error!(
                 "Error starting function '{}' for user '{}': {:?}",
                 name, user_uuid, e
             );
-            Err(ServelessCoreError::FunctionFailedToStart(name.to_string()))
-        }
-        Ok(_) => {
-            // Register the function in the cache.
-            let _ =
-                FunctionCacheRepo::add_function(cache_conn, &function_key, &addr, timeout).await;
-            info!(
-                "Function '{}' for user '{}' started at: {}",
-                name, user_uuid, addr
-            );
-            Ok(addr)
-        }
-    }
+            ServelessCoreError::FunctionFailedToStart(name.to_string())
+        })?;
+
+    // Register the function in the cache.
+    let function_address = format!(
+        "{}:{}",
+        &container_details.container_name, &container_details.container_port
+    );
+    let _ = FunctionCacheRepo::add_function(
+        cache_conn,
+        &function_key,
+        &function_address,
+        container_details.timeout,
+    )
+    .await;
+    info!(
+        "Function '{}' for user '{}' started at: {}",
+        name, user_uuid, function_address
+    );
+    Ok(function_address)
 }
diff --git a/serverless_core/src/lifecycle_manager/lib.rs b/serverless_core/src/lifecycle_manager/lib.rs
deleted file mode 100644
index de500cf..0000000
--- a/serverless_core/src/lifecycle_manager/lib.rs
+++ /dev/null
@@ -1,3 +0,0 @@
-pub(crate) mod deploy;
-mod error;
-pub(crate) mod invoke;
diff --git a/serverless_core/src/utils/utils.rs b/serverless_core/src/utils/utils.rs
index 7c4f6c2..3743660 100644
--- a/serverless_core/src/utils/utils.rs
+++ b/serverless_core/src/utils/utils.rs
@@ -5,6 +5,8 @@ use axum::http::{
 };
 use axum::response::IntoResponse;
 use hyper::body::to_bytes;
+use rand::distributions::Alphanumeric;
+use rand::{thread_rng, Rng};
 use reqwest::header::HeaderMap as ReqwestHeaderMap;
 use reqwest::Client;
 use reqwest::StatusCode as ReqwestStatusCode;
@@ -86,6 +88,22 @@ fn convert_req_header_to_axum_headers(
     }
 }
 
+/// Generates a random container name suitable for Docker
+///
+/// Returns a lowercase alphanumeric string prefixed with 'c-' to ensure it starts with a letter
+pub fn random_container_name() -> String {
+    // Generate a random 10-character string
+    let random_string: String = thread_rng()
+        .sample_iter(&Alphanumeric)
+        .take(10)
+        .map(char::from)
+        .collect::<String>()
+        .to_lowercase();
+
+    // Prefix with 'c-' to ensure it starts with a letter (Docker requirement)
+    format!("c-{}", random_string)
+}
+
 /// Generates a random port number (as a string) in the range 8000-8999.
 ///
 /// Note: This function does not guarantee that the returned port is available.
diff --git a/test-deploy.sh b/test-deploy.sh
new file mode 100755
index 0000000..2791d2d
--- /dev/null
+++ b/test-deploy.sh
@@ -0,0 +1,12 @@
+#!/bin/sh
+set -eu pipefail
+
+# invok register --email user@example.com --password your_password
+
+invok login --email user@example.com --password your_password
+
+invok create -n hello-world
+
+invok deploy -n hello-world
+
+invok list
\ No newline at end of file


Reviews:
user: login: alob-mtc
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-04T20:24:32Z
user: login: alob-mtc
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-04T20:35:12Z
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR dockerizes the invok core and refactors the internal module organization and configuration management. Key changes include:
- Reorganizing modules in the lifecycle_manager to remove the lib module and update error handling.
- Updating configuration usage to employ InvokConfig and associated server and function config structures.
- Adjusting Docker connections to use HTTP defaults along with adding a socat service in docker-compose for Docker-out-of-Docker support.

### Reviewed Changes

Copilot reviewed 19 out of 20 changed files in this pull request and generated 1 comment.

<details>
<summary>Show a summary per file</summary>

| File                                                     | Description                                              |
| -------------------------------------------------------- | -------------------------------------------------------- |
| serverless_core/src/lifecycle_manager/lib.rs             | Removed redundant module exports to align with new structure. |
| serverless_core/src/lifecycle_manager/invoke.rs          | Updated error module import path.                        |
| serverless_core/src/lifecycle_manager/deploy.rs          | Updated error module import path.                        |
| serverless_core/src/lifecycle_manager.rs                 | Refactored module declarations.                           |
| serverless_core/src/api_controller/mod.rs                | Updated configuration type and error enum naming.         |
| serverless_core/src/api_controller/middlewares/jwt.rs    | Adjusted token validation to use the new config.         |
| serverless_core/src/api_controller/handlers/*            | Updated lifecycle_manager module paths and config usage. |
| serverless_core/src/api_controller/config/*              | Introduced new InvokServerConfig and InvokFunctionConfig, and updated InvokConfig. |
| serverless_core/Cargo.toml                                | Removed dotenvy dependency.                               |
| runtime/src/core/runner.rs & provisioning.rs             | Changed Docker connection from socket to HTTP defaults.  |
| docker-compose.yml                                       | Added a socat service and updated environment configuration for Docker-out-of-Docker. |
| cli/src/serverless_function.rs & host_manager.rs         | Minor logging and host configuration updates.             |
</details>


<details>
<summary>Files not reviewed (1)</summary>

* **Dockerfile**: Language not supported
</details>



state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-04T20:36:22Z
user: login: dandax123
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-04T20:45:33Z
user: login: dandax123
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-04T20:50:09Z
user: login: dandax123
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-04T20:57:26Z
user: login: alob-mtc
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-06T12:25:32Z
user: login: dandax123
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T12:59:35Z
user: login: alob-mtc
state: APPROVED
author_association: OWNER
submitted_at: 2025-05-09T15:33:55Z

Review comments:
diff_hunk: @@ -60,7 +60,7 @@ pub async fn provisioning(
     runner_type: &str,
     dockerfile_content: &str,
 ) -> AppResult<()> {
-    let docker = Docker::connect_with_socket_defaults()
+    let docker = Docker::connect_with_http_defaults()
path: runtime/src/core/provisioning.rs
user: login: alob-mtc
body: This hard switch will block the ease of running this outside the context of docker, can you preserve both connection medium, but maintain a toggle we can control via env to choose what mode to be used depending on which env the app is running. This current approach will limit to just docker and hence hurt debug-ability 
created_at: 2025-04-30T18:34:44Z
author_association: OWNER
diff_hunk: @@ -33,7 +34,7 @@ pub async fn runner(
     timeout: Option<Duration>,
 ) -> AppResult<String> {
     // Connect to Docker via Unix socket (or named pipe on Windows).
-    let docker = Docker::connect_with_socket_defaults()
+    let docker = Docker::connect_with_http_defaults()
path: runtime/src/core/runner.rs
user: login: alob-mtc
body: same here 
created_at: 2025-04-30T18:34:55Z
author_association: OWNER
diff_hunk: @@ -60,7 +60,7 @@ pub async fn provisioning(
     runner_type: &str,
     dockerfile_content: &str,
 ) -> AppResult<()> {
-    let docker = Docker::connect_with_socket_defaults()
+    let docker = Docker::connect_with_http_defaults()
path: runtime/src/core/provisioning.rs
user: login: alob-mtc
body: the mode can be pass down as an additional function parameter to `provisioning`
created_at: 2025-04-30T18:36:21Z
author_association: OWNER
diff_hunk: @@ -1,31 +1,75 @@
-version: '3.8'
-
 services:
+  socat:
+    image: alpine/socat
+    user: root
+    command: tcp-listen:2375,fork,reuseaddr unix-connect:/var/run/docker.sock
+    restart: unless-stopped
+    networks:
+      - infra_network
+    volumes:
+      - type: bind
+        source: /var/run/docker.sock
+        target: /var/run/docker.sock
+
+  invok_core:
+    container_name: invok-core
+    build:
+      context: .
+      cache_from: []
+    depends_on:
+      - postgres
+      - redis
+      - socat
+    ports:
+      - "3000:3000"
+    networks:
+      - infra_network
+      # - app_network
+    environment:
+      DATABASE_URL: "postgres://postgres:postgres@postgres:5432/serverless"
+      REDIS_URL: "redis://redis:6379"
+      AUTH_JWT_SECRET: "your-secret-key-here"
+      RUST_LOG: "debug"
+      DOCKER_HOST: "socat:2375"
+      # TODO: for dev
+    # develop:
+    #   watch:
+    #     - action: rebuild
+    #       path: ./
+    #       ignore:
+    #         - "**/*.md"
+    #         - "**/.gitignore"
+    #     - action: rebuild
+    #       path: ./Cargo.toml
+    #     - action: rebuild
+    #       path: ./Cargo.lock
+
   postgres:
     image: postgres:15
     container_name: postgres
     environment:
-      POSTGRES_USER: root
-      POSTGRES_PASSWORD: root
-      POSTGRES_DB: serverless
+      - POSTGRES_USER=postgres
+      - POSTGRES_PASSWORD=postgres
+      - POSTGRES_DB=serverless
path: docker-compose.yml
user: login: alob-mtc
body: ```suggestion
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: serverless
```
created_at: 2025-05-04T20:35:12Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,38 @@
+use std::env;
+
+const MAX_FUNCTION_SIZE_ENV_VARIABLE: &str = "MAX_FUNCTION_SIZE";
+const DEFAULT_RUNTIME_ENV_VARIABLE: &str = "DEFAULT_RUNTIME";
+
+/// Default runtime if not specified
+pub const DEFAULT_RUNTIME_VALUE: &str = "go";
+
+/// Default maximum function size (10MB)
+pub const DEFAULT_MAX_FUNCTION_SIZE_VALUE: usize = 10 * 1024 * 1024;
+
+/// Function service configuration
+#[derive(Debug, Clone)]
+pub struct InvokFunctionConfig {
+    /// Default runtime to use for functions
+    pub default_runtime: String,
+
+    /// Maximum function size in bytes
+    pub max_function_size: usize,
+}
+
+impl InvokFunctionConfig {
+    /// Load function configuration from environment
+    pub fn from_env() -> Self {
+        let default_runtime = env::var(MAX_FUNCTION_SIZE_ENV_VARIABLE)
+            .unwrap_or_else(|_| DEFAULT_RUNTIME_VALUE.to_string());
+
+        let max_function_size = env::var(DEFAULT_RUNTIME_ENV_VARIABLE)
path: serverless_core/src/api_controller/config/function.rs
user: login: Copilot
body: The assignment of 'default_runtime' and 'max_function_size' appears to use swapped environment variables. 'default_runtime' should be loaded from DEFAULT_RUNTIME_ENV_VARIABLE, while 'max_function_size' should be loaded from MAX_FUNCTION_SIZE_ENV_VARIABLE.
```suggestion
        let default_runtime = env::var(DEFAULT_RUNTIME_ENV_VARIABLE)
            .unwrap_or_else(|_| DEFAULT_RUNTIME_VALUE.to_string());

        let max_function_size = env::var(MAX_FUNCTION_SIZE_ENV_VARIABLE)
```
created_at: 2025-05-04T20:36:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -60,7 +60,7 @@ pub async fn provisioning(
     runner_type: &str,
     dockerfile_content: &str,
 ) -> AppResult<()> {
-    let docker = Docker::connect_with_socket_defaults()
+    let docker = Docker::connect_with_http_defaults()
path: runtime/src/core/provisioning.rs
user: login: dandax123
body: I don't follow this, what do you mean by limiting to "docker" or blocking execution "outside docker". I think this is a much more scalable solution, as the Docker daemon is no longer bound to the host, meaning you can specify any `DOCKER_HOST` URL, which could be running elsewhere. 
created_at: 2025-05-04T20:45:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,31 +1,75 @@
-version: '3.8'
-
 services:
+  socat:
+    image: alpine/socat
+    user: root
+    command: tcp-listen:2375,fork,reuseaddr unix-connect:/var/run/docker.sock
+    restart: unless-stopped
+    networks:
+      - infra_network
+    volumes:
+      - type: bind
+        source: /var/run/docker.sock
+        target: /var/run/docker.sock
+
+  invok_core:
+    container_name: invok-core
+    build:
+      context: .
+      cache_from: []
+    depends_on:
+      - postgres
+      - redis
+      - socat
+    ports:
+      - "3000:3000"
+    networks:
+      - infra_network
+      # - app_network
+    environment:
+      DATABASE_URL: "postgres://postgres:postgres@postgres:5432/serverless"
+      REDIS_URL: "redis://redis:6379"
+      AUTH_JWT_SECRET: "your-secret-key-here"
+      RUST_LOG: "debug"
+      DOCKER_HOST: "socat:2375"
+      # TODO: for dev
+    # develop:
+    #   watch:
+    #     - action: rebuild
+    #       path: ./
+    #       ignore:
+    #         - "**/*.md"
+    #         - "**/.gitignore"
+    #     - action: rebuild
+    #       path: ./Cargo.toml
+    #     - action: rebuild
+    #       path: ./Cargo.lock
+
   postgres:
     image: postgres:15
     container_name: postgres
     environment:
-      POSTGRES_USER: root
-      POSTGRES_PASSWORD: root
-      POSTGRES_DB: serverless
+      - POSTGRES_USER=postgres
+      - POSTGRES_PASSWORD=postgres
+      - POSTGRES_DB=serverless
path: docker-compose.yml
user: login: dandax123
body: yeah, I also thought the samething here but apparently the "space" in the password could be a problem, but if it works, I can change it
created_at: 2025-05-04T20:50:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -60,7 +60,7 @@ pub async fn provisioning(
     runner_type: &str,
     dockerfile_content: &str,
 ) -> AppResult<()> {
-    let docker = Docker::connect_with_socket_defaults()
+    let docker = Docker::connect_with_http_defaults()
path: runtime/src/core/provisioning.rs
user: login: dandax123
body: Regardless, we could still configure this via an env. I don't want to encourage the use of direct sockets, because of the permission issue. The `invok-core` would run as a non-root container, and you won't have the root permissions to use the Docker socket directly.  So, it's best to not even have to deal with that
created_at: 2025-05-04T20:57:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,31 +1,75 @@
-version: '3.8'
-
 services:
+  socat:
+    image: alpine/socat
+    user: root
+    command: tcp-listen:2375,fork,reuseaddr unix-connect:/var/run/docker.sock
+    restart: unless-stopped
+    networks:
+      - infra_network
+    volumes:
+      - type: bind
+        source: /var/run/docker.sock
+        target: /var/run/docker.sock
+
+  invok_core:
+    container_name: invok-core
+    build:
+      context: .
+      cache_from: []
+    depends_on:
+      - postgres
+      - redis
+      - socat
+    ports:
+      - "3000:3000"
+    networks:
+      - infra_network
+      # - app_network
+    environment:
+      DATABASE_URL: "postgres://postgres:postgres@postgres:5432/serverless"
+      REDIS_URL: "redis://redis:6379"
+      AUTH_JWT_SECRET: "your-secret-key-here"
+      RUST_LOG: "debug"
+      DOCKER_HOST: "socat:2375"
+      # TODO: for dev
+    # develop:
+    #   watch:
+    #     - action: rebuild
+    #       path: ./
+    #       ignore:
+    #         - "**/*.md"
+    #         - "**/.gitignore"
+    #     - action: rebuild
+    #       path: ./Cargo.toml
+    #     - action: rebuild
+    #       path: ./Cargo.lock
+
   postgres:
     image: postgres:15
     container_name: postgres
     environment:
-      POSTGRES_USER: root
-      POSTGRES_PASSWORD: root
-      POSTGRES_DB: serverless
+      - POSTGRES_USER=postgres
+      - POSTGRES_PASSWORD=postgres
+      - POSTGRES_DB=serverless
path: docker-compose.yml
user: login: alob-mtc
body: Thanks 
created_at: 2025-05-06T12:25:31Z
author_association: OWNER
diff_hunk: @@ -1,31 +1,63 @@
-version: '3.8'
-
 services:
+  socat:
+    image: alpine/socat
+    user: root
+    command: tcp-listen:2375,fork,reuseaddr unix-connect:/var/run/docker.sock
+    restart: unless-stopped
+    networks:
+      - infra_network
+    volumes:
+      - type: bind
+        source: /var/run/docker.sock
+        target: /var/run/docker.sock
+
+  invok_core:
+    container_name: invok-core
+    build:
+      context: .
+      cache_from: []
+    depends_on:
+      - postgres
+      - redis
+      - socat
+    ports:
+      - "3000:3000"
+    networks:
+      - infra_network
+    environment:
+      DATABASE_URL: "postgres://postgres:postgres@postgres:5432/serverless"
+      REDIS_URL: "redis://redis:6379"
+      AUTH_JWT_SECRET: "your-secret-key-here"
+      RUST_LOG: "debug"
+      DOCKER_HOST: "socat:2375"
+      # It is important to set this correctly, you can find the correct value by running `docker network ls` and it should be <>_infra_network
path: docker-compose.yml
user: login: dandax123
body: Want to point out again, that you should pay attention to this
created_at: 2025-05-09T12:59:35Z
author_association: CONTRIBUTOR

PR comments:
user: login: dandax123
created_at: 2025-04-27T01:23:03Z
body: With this, it should be a lot easier to create a CI for end to end test, as we can just use the image. 

Further steps:
a. CI
b. Add building the CLI as part of the dockerfile
c. A demo/deploy dockerfile as the current one is more or less for dev
d. Configure `host` in the  CLI
author_association: CONTRIBUTOR
user: login: dandax123
created_at: 2025-04-28T15:30:02Z
body: @alob-mtc thoughts ?
author_association: CONTRIBUTOR
user: login: alob-mtc
created_at: 2025-04-30T16:45:06Z
body: Hello @dandax123, thanks for the PR, i'll take sometime to review this and hopefully we can merge this soon.
author_association: OWNER
user: login: alob-mtc
created_at: 2025-05-04T20:32:55Z
body: @dandax123 this is a good solution, thanks for the PR, also running the api-controller inside a container prevents it from been able to reach the function via "localhost" you might want to look into thing aspect. Feel free to start a conversation if you think the fix for this might be a bit more tricky, happy to brain storm potential solutions and trad-offs.
author_association: OWNER
user: login: dandax123
created_at: 2025-05-04T20:48:44Z
body: > @dandax123 this is a good solution, thanks for the PR, also running the api-controller inside a container prevents it from been able to reach the function via "localhost" you might want to look into thing aspect. Feel free to start a conversation if you think the fix for this might be a bit more tricky, happy to brain storm potential solutions and trad-offs.

The answer to this is 
a. You don't want to be able to reach a function directly, i.e running on host network, as this obviously takes away all the security guarantees, and isolation properties of docker. Also, it limits (theoretically) the amount of allocable ports.

b. To fix this, you just need a reverse proxy  -> nginx or something else, which also helps for other areas like load balancing. I can add this  in a new PR.  

 
author_association: CONTRIBUTOR
user: login: alob-mtc
created_at: 2025-05-06T12:14:15Z
body: > b. To fix this, you just need a reverse proxy -> nginx or something else, which also helps for other areas like load balancing. I can add this in a new PR.

it's important to have a working version of the system as every-point in time, it will be nice if the fix added as part of this PR
author_association: OWNER
user: login: dandax123
created_at: 2025-05-06T12:44:31Z
body: > > b. To fix this, you just need a reverse proxy -> nginx or something else, which also helps for other areas like load balancing. I can add this in a new PR.
> 
> it's important to have a working version of the system as every-point in time, it will be nice if the fix added as part of this PR

A. The actual invok container shouldn't be accessible on host (except by explicit configuring)

B. To access the invok, we should do this by a generated url (which I believe should still work, if it's designed that way) as the app controller should still be accessible over localhost:3000

Regardless, I'll look at the current design and fix it, if necessary 



author_association: CONTRIBUTOR
user: login: alob-mtc
created_at: 2025-05-06T12:48:41Z
body: @dandax123 i believe my point may not have been clear. Moving to this approach is welcome, and is a good change. The change however has broken something.
A - Function deployment works fine
B - Function invocation is broken. This requires fix before we can merge this change.

For any change introduced to the system A & B must both work fine.
author_association: OWNER
user: login: dandax123
created_at: 2025-05-07T12:52:24Z
body: @alob-mtc I think I'm not fully on the same page with you. When you say invocation is broken.

Let's say invok core is the api-controller and invok is the "deployed functon" (p.s we do need a better name for this)


I assume that you want to start the invok container and also access that by `localhost:<random_port>` ?

Invocation to me, means that you can start the deployed function container. Which I believe works, because I ran using the cli the example in the readme

But maybe you have an access problem ?

I'm still confused lol

author_association: CONTRIBUTOR
user: login: dandax123
created_at: 2025-05-07T12:57:37Z
body: What I envision should be the ideal case is that you deployed the function, and the you get a url.

And that url (e.g `localhost:3000/<project-id>/namespace/func-name-xxx`) and you use that url to invoke and send parameters. Assuming invok-core is on localhost:3000

I think this is the current design ?
author_association: CONTRIBUTOR
user: login: alob-mtc
created_at: 2025-05-08T17:38:36Z
body: "localhost:3000/<project-id>/namespace/func-name-xxx" does not work anymore that's what I'm saying.

Does it work on your end? Can you confirm this 
author_association: OWNER
user: login: dandax123
created_at: 2025-05-09T13:02:29Z
body: @alob-mtc This contains the fix. I will do a PR later to properly add  project & namespace so the hierarchy for invocation is `<project-id>/<namespace>/<function-name>`. 

The fix explanation:
a. add the created container to the docker compose network, so it can be resolved by docker DNS using the container name
b. actually expose the container, because it wasn't before

author_association: CONTRIBUTOR
user: login: alob-mtc
created_at: 2025-05-09T15:18:23Z
body: Adding that build_cli script is really handy 👌🏾
author_association: OWNER
user: login: alob-mtc
created_at: 2025-05-09T15:38:58Z
body: > I will do a PR later to properly add project & namespace so the hierarchy for invocation is \<project-id>\/\<namespace>\/\<function-name>.

@dandax123 please raise an issue for this and share what this will look like before you raise a PR thanks.
author_association: OWNER