diff --git a/app/assets/css/react-datetime-picker-override.css b/app/assets/css/react-datetime-picker-override.css
index f834265ce..acd26fb58 100644
--- a/app/assets/css/react-datetime-picker-override.css
+++ b/app/assets/css/react-datetime-picker-override.css
@@ -5,82 +5,110 @@
   library css for buttons is overriden by `.widget .widget-body button`
   so we have to force margin: 0
 */
-.react-datetime-picker .react-calendar button {
+.react-daterange-picker__calendar .react-calendar button {
   margin: 0 !important;
 }
 
 /*
-  Extending Calendar.css from react-datetime-picker
+  Extending Calendar.css from react-daterange-picker__calendar
 */
-.react-datetime-picker .react-calendar {
+.react-daterange-picker__calendar .react-calendar {
   background: var(--bg-calendar-color);
   color: var(--text-main-color);
 }
 
 /* calendar nav buttons */
-.react-datetime-picker .react-calendar__navigation button:disabled {
-  background-color: var(--bg-calendar-color);
+.react-daterange-picker__calendar .react-calendar__navigation button:disabled {
+  background: var(--bg-calendar-color);
   @apply opacity-60;
   @apply brightness-95 th-dark:brightness-110;
 }
-.react-datetime-picker .react-calendar__navigation button:enabled:hover,
-.react-datetime-picker .react-calendar__navigation button:enabled:focus {
-  background-color: var(--bg-daterangepicker-color);
+.react-daterange-picker__calendar .react-calendar__navigation button:enabled:hover,
+.react-daterange-picker__calendar .react-calendar__navigation button:enabled:focus {
+  background: var(--bg-daterangepicker-color);
 }
 
 /* date tile */
-.react-datetime-picker .react-calendar__tile:disabled {
-  background-color: var(--bg-calendar-color);
+.react-daterange-picker__calendar .react-calendar__tile:disabled {
+  background: var(--bg-calendar-color);
   @apply opacity-60;
   @apply brightness-95 th-dark:brightness-110;
 }
-.react-datetime-picker .react-calendar__tile:enabled:hover,
-.react-datetime-picker .react-calendar__tile:enabled:focus {
-  background-color: var(--bg-daterangepicker-hover);
+.react-daterange-picker__calendar .react-calendar__tile:enabled:hover,
+.react-daterange-picker__calendar .react-calendar__tile:enabled:focus {
+  background: var(--bg-daterangepicker-hover);
 }
 
 /* today's date tile */
-.react-datetime-picker .react-calendar__tile--now {
-  /* use background color to avoid white on yellow in dark/high contrast modes */
+.react-daterange-picker__calendar .react-calendar__tile--now {
   @apply th-highcontrast:text-[color:var(--bg-calendar-color)] th-dark:text-[color:var(--bg-calendar-color)];
+  border-radius: 0.25rem !important;
 }
-.react-datetime-picker .react-calendar__tile--now:enabled:hover,
-.react-datetime-picker .react-calendar__tile--now:enabled:focus {
+.react-daterange-picker__calendar .react-calendar__tile--now:enabled:hover,
+.react-daterange-picker__calendar .react-calendar__tile--now:enabled:focus {
   background: var(--bg-daterangepicker-hover);
   color: var(--text-daterangepicker-hover);
 }
 
 /* probably date tile in range */
-.react-datetime-picker .react-calendar__tile--hasActive {
+.react-daterange-picker__calendar .react-calendar__tile--hasActive {
   background: var(--bg-daterangepicker-end-date);
   color: var(--text-daterangepicker-end-date);
 }
-.react-datetime-picker .react-calendar__tile--hasActive:enabled:hover,
-.react-datetime-picker .react-calendar__tile--hasActive:enabled:focus {
+.react-daterange-picker__calendar .react-calendar__tile--hasActive:enabled:hover,
+.react-daterange-picker__calendar .react-calendar__tile--hasActive:enabled:focus {
   background: var(--bg-daterangepicker-hover);
   color: var(--text-daterangepicker-hover);
 }
 
-/* selected date tile */
-.react-datetime-picker .react-calendar__tile--active {
-  background: var(--bg-daterangepicker-active);
-  color: var(--text-daterangepicker-active);
-}
-.react-datetime-picker .react-calendar__tile--active:enabled:hover,
-.react-datetime-picker .react-calendar__tile--active:enabled:focus {
+.react-daterange-picker__calendar .react-calendar__tile--active:enabled:hover,
+.react-daterange-picker__calendar .react-calendar__tile--active:enabled:focus {
   background: var(--bg-daterangepicker-hover);
   color: var(--text-daterangepicker-hover);
 }
 
+.react-daterange-picker__calendar
+  .react-calendar__month-view__days__day:hover:not(.react-daterange-picker__calendar .react-calendar__tile--hoverEnd):not(
+    .react-daterange-picker__calendar .react-calendar__tile--hoverStart
+  ):not(.react-calendar__tile--active) {
+  border-radius: 0.25rem !important;
+}
+
 /* on range select hover */
-.react-datetime-picker .react-calendar--selectRange .react-calendar__tile--hover {
-  background-color: var(--bg-daterangepicker-in-range);
+.react-daterange-picker__calendar .react-calendar--selectRange .react-calendar__tile--hover {
+  background: var(--bg-daterangepicker-in-range);
   color: var(--text-daterangepicker-in-range);
 }
 
 /*
-  Extending DateTimePicker.css from react-datetime-picker
+  Extending DateTimePicker.css from react-daterange-picker__calendar
 */
-.react-datetime-picker .react-datetime-picker--disabled {
+.react-daterange-picker__calendar .react-daterange-picker__calendar--disabled {
   @apply opacity-40;
 }
+
+/* selected date tile */
+.react-daterange-picker__calendar .react-calendar__tile--active {
+  background: var(--bg-daterangepicker-active) !important;
+  color: var(--text-daterangepicker-active) !important;
+}
+
+.react-daterange-picker__calendar .react-calendar__tile--rangeStart:not(.react-calendar__tile--rangeEnd),
+.react-daterange-picker__calendar .react-calendar__tile--hoverStart {
+  border-top-left-radius: 0.25rem;
+  border-bottom-left-radius: 0.25rem;
+}
+
+.react-daterange-picker__calendar .react-calendar__tile--rangeEnd:not(.react-calendar__tile--rangeStart),
+.react-daterange-picker__calendar .react-calendar__tile--hoverEnd {
+  border-top-right-radius: 0.25rem;
+  border-bottom-right-radius: 0.25rem;
+}
+
+.react-daterange-picker__calendar .react-calendar__month-view__days__day--weekend {
+  color: inherit;
+}
+
+.react-calendar__tile--active.react-calendar__month-view__days__day--weekend {
+  color: var(--text-daterangepicker-active);
+}
diff --git a/app/react/docker/containers/CreateView/BaseForm/PortsMappingField.viewModel.ts b/app/react/docker/containers/CreateView/BaseForm/PortsMappingField.viewModel.ts
index fa1cc11c7..16f5b4363 100644
--- a/app/react/docker/containers/CreateView/BaseForm/PortsMappingField.viewModel.ts
+++ b/app/react/docker/containers/CreateView/BaseForm/PortsMappingField.viewModel.ts
@@ -14,13 +14,8 @@ type StringPortBinding = {
   containerPort: number;
 };
 
-type NumericPortBinding = {
-  hostPort: number;
-  protocol: Protocol;
-  containerPort: number;
-};
-
 type RangePortBinding = {
+  hostIp: string;
   hostPort: Range;
   protocol: Protocol;
   containerPort: Range;
@@ -42,9 +37,7 @@ export function toViewModel(portBindings: PortMap): Values {
     return value === 'tcp' || value === 'udp';
   }
 
-  function parsePorts(
-    portBindings: PortMap
-  ): Array<StringPortBinding | NumericPortBinding> {
+  function parsePorts(portBindings: PortMap): Array<StringPortBinding> {
     return Object.entries(portBindings).flatMap(([key, bindings]) => {
       const [containerPort, protocol] = key.split('/');
 
@@ -63,15 +56,24 @@ export function toViewModel(portBindings: PortMap): Values {
       }
 
       return bindings.map((binding) => {
+        let port = '';
+        if (binding.HostPort) {
+          port = binding.HostPort;
+        }
+        if (binding.HostIp) {
+          port = `${binding.HostIp}:${port}`;
+        }
+
         if (binding.HostPort?.includes('-')) {
+          // Range port
           return {
-            hostPort: binding.HostPort,
+            hostPort: port,
             protocol,
             containerPort: containerPortNumber,
           };
         }
         return {
-          hostPort: parseInt(binding.HostPort || '0', 10),
+          hostPort: port,
           protocol,
           containerPort: containerPortNumber,
         };
@@ -79,9 +81,9 @@ export function toViewModel(portBindings: PortMap): Values {
     });
   }
 
-  function sortPorts(ports: Array<StringPortBinding | NumericPortBinding>) {
-    const rangePorts = ports.filter(isStringPortBinding);
-    const nonRangePorts = ports.filter(isNumericPortBinding);
+  function sortPorts(ports: Array<StringPortBinding>) {
+    const rangePorts = ports.filter(isRangePortBinding);
+    const nonRangePorts = ports.filter((port) => !isRangePortBinding(port));
 
     return {
       rangePorts,
@@ -93,27 +95,40 @@ export function toViewModel(portBindings: PortMap): Values {
     };
   }
 
-  function combinePorts(ports: Array<NumericPortBinding>) {
+  function combinePorts(ports: Array<StringPortBinding>) {
     return ports
       .reduce((acc, port) => {
+        let hostIp = '';
+        let hostPort = 0;
+        if (port.hostPort.includes(':')) {
+          const [ipStr, portStr] = port.hostPort.split(':');
+          hostIp = ipStr;
+          hostPort = parseInt(portStr || '0', 10);
+        } else {
+          hostPort = parseInt(port.hostPort || '0', 10);
+        }
+
         const lastPort = acc[acc.length - 1];
         if (
           lastPort &&
+          lastPort.hostIp === hostIp &&
           lastPort.containerPort.end === port.containerPort - 1 &&
-          lastPort.hostPort.end === port.hostPort - 1 &&
+          lastPort.hostPort.end === hostPort - 1 &&
           lastPort.protocol === port.protocol
         ) {
+          lastPort.hostIp = hostIp;
           lastPort.containerPort.end = port.containerPort;
-          lastPort.hostPort.end = port.hostPort;
+          lastPort.hostPort.end = hostPort;
           return acc;
         }
 
         return [
           ...acc,
           {
+            hostIp,
             hostPort: {
-              start: port.hostPort,
-              end: port.hostPort,
+              start: hostPort,
+              end: hostPort,
             },
             containerPort: {
               start: port.containerPort,
@@ -123,34 +138,32 @@ export function toViewModel(portBindings: PortMap): Values {
           },
         ];
       }, [] as Array<RangePortBinding>)
-      .map(({ protocol, containerPort, hostPort }) => ({
-        hostPort: getRange(hostPort.start, hostPort.end),
+      .map(({ protocol, containerPort, hostPort, hostIp }) => ({
+        hostPort: getRange(hostPort.start, hostPort.end, hostIp),
         containerPort: getRange(containerPort.start, containerPort.end),
         protocol,
       }));
 
-    function getRange(start: number, end: number): string {
+    function getRange(start: number, end: number, hostIp?: string): string {
       if (start === end) {
         if (start === 0) {
           return '';
         }
 
+        if (hostIp) {
+          return `${hostIp}:${start}`;
+        }
         return start.toString();
       }
 
+      if (hostIp) {
+        return `${hostIp}:${start}-${end}`;
+      }
       return `${start}-${end}`;
     }
   }
 }
 
-function isNumericPortBinding(
-  port: StringPortBinding | NumericPortBinding
-): port is NumericPortBinding {
-  return port.hostPort !== 'string';
-}
-
-function isStringPortBinding(
-  port: StringPortBinding | NumericPortBinding
-): port is StringPortBinding {
-  return port.hostPort === 'string';
+function isRangePortBinding(port: StringPortBinding): boolean {
+  return port.hostPort.includes('-');
 }
diff --git a/app/react/docker/images/queries/useBuildImageMutation.ts b/app/react/docker/images/queries/useBuildImageMutation.ts
index 557fd6ad3..ee9eca5df 100644
--- a/app/react/docker/images/queries/useBuildImageMutation.ts
+++ b/app/react/docker/images/queries/useBuildImageMutation.ts
@@ -57,10 +57,15 @@ export async function buildImageFromDockerfileContentAndFiles(
   const dockerfile = new Blob([content], { type: 'text/plain' });
   const uploadFiles = [dockerfile, ...files];
 
+  const formData = new FormData();
+  uploadFiles.forEach((file, index) => {
+    formData.append(`file${index}`, file);
+  });
+
   return buildImage(
     environmentId,
     { t: names },
-    { file: uploadFiles },
+    formData,
     'multipart/form-data'
   );
 }
diff --git a/app/react/portainer/logs/ActivityLogsView/types.ts b/app/react/portainer/logs/ActivityLogsView/types.ts
index 237c8a257..76176e7ad 100644
--- a/app/react/portainer/logs/ActivityLogsView/types.ts
+++ b/app/react/portainer/logs/ActivityLogsView/types.ts
@@ -1,8 +1,19 @@
-export interface ActivityLog {
+interface BaseActivityLog {
   timestamp: number;
   action: string;
   context: string;
   id: number;
-  payload: object;
   username: string;
 }
+export interface ActivityLogResponse extends BaseActivityLog {
+  payload: string;
+}
+
+export interface ActivityLog extends BaseActivityLog {
+  payload: string | object;
+}
+
+export interface ActivityLogsResponse {
+  logs: Array<ActivityLogResponse>;
+  totalCount: number;
+}
diff --git a/app/react/portainer/logs/ActivityLogsView/useActivityLogs.ts b/app/react/portainer/logs/ActivityLogsView/useActivityLogs.ts
index 65a246f10..f55b64416 100644
--- a/app/react/portainer/logs/ActivityLogsView/useActivityLogs.ts
+++ b/app/react/portainer/logs/ActivityLogsView/useActivityLogs.ts
@@ -4,7 +4,7 @@ import axios, { parseAxiosError } from '@/portainer/services/axios';
 
 import { isBE } from '../../feature-flags/feature-flags.service';
 
-import { ActivityLog } from './types';
+import { ActivityLogResponse, ActivityLogsResponse } from './types';
 
 export const sortKeys = ['Context', 'Action', 'Timestamp', 'Username'] as const;
 export type SortKey = (typeof sortKeys)[number];
@@ -30,19 +30,18 @@ export function useActivityLogs(query: Query) {
     queryKey: ['activityLogs', query] as const,
     queryFn: () => fetchActivityLogs(query),
     keepPreviousData: true,
+    select: (data) => ({
+      ...data,
+      logs: decorateLogs(data.logs),
+    }),
   });
 }
 
-interface ActivityLogsResponse {
-  logs: Array<ActivityLog>;
-  totalCount: number;
-}
-
 async function fetchActivityLogs(query: Query): Promise<ActivityLogsResponse> {
   try {
     if (!isBE) {
       return {
-        logs: [{}, {}, {}, {}, {}] as Array<ActivityLog>,
+        logs: [{}, {}, {}, {}, {}] as Array<ActivityLogResponse>,
         totalCount: 5,
       };
     }
@@ -56,3 +55,40 @@ async function fetchActivityLogs(query: Query): Promise<ActivityLogsResponse> {
     throw parseAxiosError(err, 'Failed loading user activity logs csv');
   }
 }
+
+/**
+ * Decorates logs with the payload parsed from base64
+ */
+function decorateLogs(logs?: ActivityLogResponse[]) {
+  if (!logs || logs.length === 0) {
+    return [];
+  }
+
+  return logs.map((log) => ({
+    ...log,
+    payload: parseBase64AsObject(log.payload),
+  }));
+}
+
+function parseBase64AsObject(value: string): string | object {
+  if (!value) {
+    return value;
+  }
+  try {
+    return JSON.parse(safeAtob(value));
+  } catch (err) {
+    return safeAtob(value);
+  }
+}
+
+function safeAtob(value: string) {
+  if (!value) {
+    return value;
+  }
+  try {
+    return window.atob(value);
+  } catch (err) {
+    // If the payload is not base64 encoded, return the original value
+    return value;
+  }
+}
diff --git a/pkg/libstack/compose/composeplugin.go b/pkg/libstack/compose/composeplugin.go
index f2d898dc2..1990a95ed 100644
--- a/pkg/libstack/compose/composeplugin.go
+++ b/pkg/libstack/compose/composeplugin.go
@@ -70,32 +70,24 @@ func withComposeService(
 	return withCli(ctx, options, func(ctx context.Context, cli *command.DockerCli) error {
 		composeService := compose.NewComposeService(cli)
 
+		if len(filePaths) == 0 {
+			return composeFn(composeService, nil)
+		}
+
+		env, err := parseEnvironment(options)
+		if err != nil {
+			return err
+		}
+
 		configDetails := types.ConfigDetails{
-			WorkingDir:  options.WorkingDir,
-			Environment: make(map[string]string),
+			Environment: env,
+			WorkingDir:  filepath.Dir(filePaths[0]),
 		}
 
 		for _, p := range filePaths {
 			configDetails.ConfigFiles = append(configDetails.ConfigFiles, types.ConfigFile{Filename: p})
 		}
 
-		envFile := make(map[string]string)
-
-		if options.EnvFilePath != "" {
-			env, err := dotenv.GetEnvFromFile(make(map[string]string), []string{options.EnvFilePath})
-			if err != nil {
-				return fmt.Errorf("unable to get the environment from the env file: %w", err)
-			}
-
-			maps.Copy(envFile, env)
-
-			configDetails.Environment = env
-		}
-
-		if len(configDetails.ConfigFiles) == 0 {
-			return composeFn(composeService, nil)
-		}
-
 		project, err := loader.LoadWithContext(ctx, configDetails,
 			func(o *loader.Options) {
 				o.SkipResolveEnvironment = true
@@ -110,21 +102,20 @@ func withComposeService(
 			return fmt.Errorf("failed to load the compose file: %w", err)
 		}
 
-		if options.EnvFilePath != "" {
-			// Work around compose path handling
-			for i, service := range project.Services {
-				for j, envFile := range service.EnvFiles {
-					if !filepath.IsAbs(envFile.Path) {
-						project.Services[i].EnvFiles[j].Path = filepath.Join(project.WorkingDir, envFile.Path)
-					}
+		// Work around compose path handling
+		for i, service := range project.Services {
+			for j, envFile := range service.EnvFiles {
+				if !filepath.IsAbs(envFile.Path) {
+					project.Services[i].EnvFiles[j].Path = filepath.Join(configDetails.WorkingDir, envFile.Path)
 				}
 			}
+		}
 
-			if p, err := project.WithServicesEnvironmentResolved(true); err == nil {
-				project = p
-			} else {
-				return fmt.Errorf("failed to resolve services environment: %w", err)
-			}
+		// Set the services environment variables
+		if p, err := project.WithServicesEnvironmentResolved(true); err == nil {
+			project = p
+		} else {
+			return fmt.Errorf("failed to resolve services environment: %w", err)
 		}
 
 		return composeFn(composeService, project)
@@ -136,6 +127,8 @@ func (c *ComposeDeployer) Deploy(ctx context.Context, filePaths []string, option
 	return withComposeService(ctx, filePaths, options.Options, func(composeService api.Service, project *types.Project) error {
 		addServiceLabels(project, false)
 
+		project = project.WithoutUnnecessaryResources()
+
 		var opts api.UpOptions
 		if options.ForceRecreate {
 			opts.Create.Recreate = api.RecreateForce
@@ -144,6 +137,10 @@ func (c *ComposeDeployer) Deploy(ctx context.Context, filePaths []string, option
 		opts.Create.RemoveOrphans = options.RemoveOrphans
 		opts.Start.CascadeStop = options.AbortOnContainerExit
 
+		if err := composeService.Build(ctx, project, api.BuildOptions{}); err != nil {
+			return fmt.Errorf("compose build operation failed: %w", err)
+		}
+
 		if err := composeService.Up(ctx, project, opts); err != nil {
 			return fmt.Errorf("compose up operation failed: %w", err)
 		}
@@ -256,10 +253,36 @@ func addServiceLabels(project *types.Project, oneOff bool) {
 			api.ProjectLabel:     project.Name,
 			api.ServiceLabel:     s.Name,
 			api.VersionLabel:     api.ComposeVersion,
-			api.WorkingDirLabel:  "/",
+			api.WorkingDirLabel:  project.WorkingDir,
 			api.ConfigFilesLabel: strings.Join(project.ComposeFiles, ","),
 			api.OneoffLabel:      oneOffLabel,
 		}
 		project.Services[i] = s
 	}
 }
+
+func parseEnvironment(options libstack.Options) (map[string]string, error) {
+	env := make(map[string]string)
+
+	for _, envLine := range options.Env {
+		e, err := dotenv.UnmarshalWithLookup(envLine, nil)
+		if err != nil {
+			return nil, fmt.Errorf("unable to parse environment variables: %w", err)
+		}
+
+		maps.Copy(env, e)
+	}
+
+	if options.EnvFilePath == "" {
+		return env, nil
+	}
+
+	e, err := dotenv.GetEnvFromFile(make(map[string]string), []string{options.EnvFilePath})
+	if err != nil {
+		return nil, fmt.Errorf("unable to get the environment from the env file: %w", err)
+	}
+
+	maps.Copy(env, e)
+
+	return env, nil
+}
diff --git a/vitest.config.mts b/vitest.config.mts
index 89caa757a..653cb488d 100644
--- a/vitest.config.mts
+++ b/vitest.config.mts
@@ -14,6 +14,9 @@ export default defineConfig({
     },
     bail: 2,
     include: ['./app/**/*.test.ts', './app/**/*.test.tsx'],
+    env: {
+      PORTAINER_EDITION: 'CE',
+    },
   },
   plugins: [svgr({ include: /\?c$/ }), tsconfigPaths()],
 });