I recently managed to deploy an instance of nopCommerce (version 4.70.0) using the original source code on docker-compose.
Then I developed a custom plugin (it's just a simple plugin to enabling users to login by their cellphone and OTP). But when I tried to deploy it by using source-code and re-creating docker image/container, I realized that my plugin doesn't get copied to /app/published folder in the container. That was passed because I just wanted to get it work. So, I uploaded the plugin through admin panel.
After that, I developed a simple theme (simply by copying the default theme) and tried to publish it by re-building and recreating docker image and container. But, again, I realized the theme folder doesn't get copied to container, same as what was happening by my custom plugin. I think I did everything right, but it just doesn't work. Here is my Nop.Web.csproj
:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Copyright>Copyright © Nop Solutions, Ltd</Copyright>
<Company>Nop Solutions, Ltd</Company>
<Authors>Nop Solutions, Ltd</Authors>
<Version>4.70.0</Version>
<Description>Nop.Web is also an MVC web application project, a presentation layer for public store and admin area.</Description>
<PackageLicenseUrl>https://www.nopcommerce.com/license</PackageLicenseUrl>
<PackageProjectUrl>https://www.nopcommerce.com/</PackageProjectUrl>
<RepositoryUrl>https://github.com/nopSolutions/nopCommerce</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<ImplicitUsings>enable</ImplicitUsings>
<!--Starting with the .NET 6 SDK, the [Appname].runtimesettings.dev.json file is no longer generated by default at compile time. If you still want this file to be generated, set the GenerateRuntimeConfigDevFile property to true.-->
<GenerateRuntimeConfigDevFile>true</GenerateRuntimeConfigDevFile>
<!--Set this parameter to true to get the dlls copied from the NuGet cache to the output of your project-->
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
<!--When true, compiles and emits the Razor assembly as part of publishing the project-->
<RazorCompileOnPublish>false</RazorCompileOnPublish>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Libraries\Nop.Core\Nop.Core.csproj" />
<ProjectReference Include="..\..\Libraries\Nop.Data\Nop.Data.csproj" />
<ProjectReference Include="..\..\Libraries\Nop.Services\Nop.Services.csproj" />
<ProjectReference Include="..\Nop.Web.Framework\Nop.Web.Framework.csproj" />
</ItemGroup>
<ItemGroup>
<!-- We copy the entire \App_Data directory. But we ignore JSON files and data protection keys -->
<Content Include="App_Data\**" CopyToPublishDirectory="PreserveNewest" Exclude="App_Data\*.json" />
<Content Remove="App_Data\*.json" />
<Content Update="App_Data\DataProtectionKeys\*.xml" CopyToPublishDirectory="Never" />
<Compile Remove="Plugins\**;Themes\**" />
<Content Remove="Plugins\**;Themes\**" />
<EmbeddedResource Remove="Plugins\**;Themes\**" />
<None Remove="Plugins\**;Themes\**" />
<None Include="Plugins\**" CopyToPublishDirectory="PreserveNewest" Exclude="Plugins\**\runtimes\**;Plugins\**\ref\**;Plugins\**\*.pdb" />
<Content Include="Themes\**" CopyToPublishDirectory="PreserveNewest" CopyToOutputDirectory="Never" />
<!-- We copy the \Logs directory -->
<Content Include="Logs\**" CopyToPublishDirectory="PreserveNewest" />
<None Remove="Plugins\Uploaded\placeholder.txt" />
<Content Include="Plugins\Uploaded\placeholder.txt">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<None Update="Areas\Admin\sitemap.config">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<!-- This setting fixes the problem caused by this update in the websdk in vs2019
https://github.com/aspnet/websdk/commit/7e6b193ddcf1eec5c0a88a9748c626775555273e#diff-edf5a48ed0d4aa5a4289cb857bf46a04
Therefore, we restore the standard configuration behavior (there was no copy to the output directory)
in order to avoid the "Duplicate dll" error during publication.
We can also use “ExcludeConfigFilesFromBuildOutput” according to https://github.com/aspnet/AspNetCore/issues/14017 -->
<Content Update="**\*.config;**\*.json" CopyToOutputDirectory="Never" CopyToPublishDirectory="PreserveNewest" />
</ItemGroup>
<!-- This target execute after "Build" target.
We use it to clean up folder with plugins from unnecessary and obsolete libraries. -->
<Target Name="NopTarget" AfterTargets="Build">
<ItemGroup>
<!-- Get plugin description files to get plugin paths -->
<PluginsDescription Include="$(MSBuildProjectDirectory)\Plugins\**\plugin.json;" />
<!-- Get paths for all plugins -->
<PluginsFolders Include="@(PluginsDescription->'%(relativedir)')" />
<!-- Get paths for ClearPluginAssemblies project -->
<ClearPluginAssemblies Include="$(MSBuildProjectDirectory)\..\..\Build\ClearPluginAssemblies.proj" />
</ItemGroup>
<PropertyGroup>
<PluginsFolders>@(PluginsFolders)</PluginsFolders>
</PropertyGroup>
<!-- When .NET Core builds a project, it copies all referenced libraries to the output folder.
For plugins it creates too many unnecessary files that just take up space.
At the moment you can't disable this behavior. That's why we have to manually delete all unnecessary libraries from plugin output directories. -->
<MSBuild Projects="@(ClearPluginAssemblies)" Properties="PluginPath=$(PluginsFolders)" Targets="NopClear" />
</Target>
<PropertyGroup>
<!--The common language runtime (CLR) supports two types of garbage collection:
workstation garbage collection, which is available on all systems, and server garbage collection,
which is available on multiprocessor systems.
For single-processor computers, the default workstation garbage collection should be the fastest option.
Either workstation or server can be used for two-processor computers.
Server garbage collection should be the fastest option for more than two processors.
More details about GC you can see here: https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/fundamentals-->
<ServerGarbageCollection>false</ServerGarbageCollection>
<!--In workstation or server garbage collection, you can enable concurrent garbage collection,
which enables threads to run concurrently with a dedicated thread that performs the garbage
collection for most of the duration of the collection.
Concurrent garbage collection enables interactive applications to be more responsive by
minimizing pauses for a collection. Managed threads can continue to run most of the time while
the concurrent garbage collection thread is running. This results in shorter pauses while
a garbage collection is occurring.
To improve performance when several processes are running, disable concurrent garbage collection.
More details here: https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/fundamentals#concurrent-garbage-collection-->
<ConcurrentGarbageCollection>false</ConcurrentGarbageCollection>
</PropertyGroup>
</Project>
As you can see, it's the original csproj
and I didn't change anything. And you can see these lines:
<Compile Remove="Plugins\**;Themes\**" />
<Content Remove="Plugins\**;Themes\**" />
<EmbeddedResource Remove="Plugins\**;Themes\**" />
<None Remove="Plugins\**;Themes\**" />
<None Include="Plugins\**" CopyToPublishDirectory="PreserveNewest" Exclude="Plugins\**\runtimes\**;Plugins\**\ref\**;Plugins\**\*.pdb" />
<Content Include="Themes\**" CopyToPublishDirectory="PreserveNewest" CopyToOutputDirectory="Never" />
Which theoretically should copy everything in Theme folder to the output. It does copy the default theme (DefaultClean
), but not my custom theme with exactly same structure. Also here is my Dockerfile:
# create the build instance
FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS build
WORKDIR /src
COPY ./src ./
WORKDIR /src/Presentation/Nop.Web
# build project
RUN dotnet build Nop.Web.csproj -c Release
# build plugins
WORKDIR /src/Plugins
RUN set -eux; \
for dir in *; do \
if [ -d "$dir" ]; then \
dotnet build "$dir/$dir.csproj" -c Release; \
fi; \
done
# publish project
WORKDIR /src/Presentation/Nop.Web
RUN dotnet publish Nop.Web.csproj -c Release -o /app/published
WORKDIR /app/published
RUN mkdir logs bin
RUN chmod 775 App_Data \
App_Data/DataProtectionKeys \
bin \
logs \
Plugins \
wwwroot/bundles \
wwwroot/db_backups \
wwwroot/files/exportimport \
wwwroot/icons \
wwwroot/images \
wwwroot/images/thumbs \
wwwroot/images/uploaded \
wwwroot/sitemaps
# create the runtime instance
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine AS runtime
# add globalization support
RUN apk add --no-cache icu-libs icu-data-full
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false
# installs required packages
RUN apk add tiff --no-cache --repository http://dl-3.alpinelinux.org/alpine/edge/main/ --allow-untrusted
RUN apk add libgdiplus --no-cache --repository http://dl-3.alpinelinux.org/alpine/edge/community/ --allow-untrusted
RUN apk add libc-dev tzdata --no-cache
# copy entrypoint script
COPY ./entrypoint.sh /entrypoint.sh
RUN chmod 755 /entrypoint.sh
WORKDIR /app
COPY --from=build /app/published .
ENV ASPNETCORE_URLS=http://+:80
EXPOSE 80
ENTRYPOINT "/entrypoint.sh"
Also, if it's worth to mentioning, I have this simple compose file:
services:
mynop:
container_name: mynop
hostname: mynop
image: jd/mynop
restart: always
build:
context: .
dockerfile: Dockerfile
volumes:
- mynop:/app
env_file:
- ./.env # some simple env values. nothing important
ports:
- "${WEB_EXPOSE_PORT}:80"
networks:
default:
infra:
volumes:
mynop:
name: mynop
Have you any idea why my custom codes (plugins and themes) cannot get to the final running directory? Thanks in advance.
OK. It seems there was a problem with volumes. Since I was mounting the root of application (/app
), docker wasn't changing the items in it - I guess. So I changed the volumes like this and now everything work just fine:
services:
mynop:
volumes:
- mynop_App_Data:/app/App_Data
- mynop_logs:/app/logs
- mynop_wwwroot__db_backups:/app/wwwroot/db_backups
- mynop_wwwroot__files__exportimport:/app/wwwroot/files/exportimport
- mynop_wwwroot__icons:/app/wwwroot/icons
- mynop_wwwroot__images:/app/wwwroot/images
- mynop_wwwroot__images__thumbs:/app/wwwroot/images/thumbs
- mynop_wwwroot__images__uploaded:/app/wwwroot/images/uploaded
- mynop_wwwroot__sitemaps:/app/wwwroot/sitemaps
Also you have to modify the volumes
part:
volumes:
mynop_App_Data:
name: mynop_App_Data
mynop_logs:
name: mynop_logs
mynop_wwwroot__db_backups:
name: mynop_wwwroot__db_backups
mynop_wwwroot__files__exportimport:
name: mynop_wwwroot__files__exportimport
mynop_wwwroot__icons:
name: mynop_wwwroot__icons
mynop_wwwroot__images:
name: mynop_wwwroot__images
mynop_wwwroot__images__thumbs:
name: mynop_wwwroot__images__thumbs
mynop_wwwroot__images__uploaded:
name: mynop_wwwroot__images__uploaded
mynop_wwwroot__sitemaps:
name: mynop_wwwroot__sitemaps