Installing apps on Linux with Native Docker

Hello, I know this is the most famous question when developing Litium application, the installation process for apps and dependencies through Docker.

I switched to Linux this christmas and I thought that docker should be my least problem. Everything works except installing apps. Docker Desktop on Windows and Mac does alot of things out of the box. With native Docker you have to set it up all yourself. I got so far so that I can access the apps and get redirected to Litium BO for installation, then comes the “app could not be installed” banner and the logs below from the app and LitiumAccelerator

direct-payment.log:

2025-02-07 15:07:10.1003 [App:01] [9f7d1ff3e0fd3afff9493a7fdf037843] [ERROR] [.NET ThreadPool Worker] Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler - Exception occurred while processing message. System.InvalidOperationException: IDX20803: Unable to obtain configuration from: 'System.String'.
 ---> System.IO.IOException: IDX20804: Unable to retrieve document from: 'System.String'.
 ---> System.Net.Http.HttpRequestException: Name or service not known (pk-produkter.localtest.me:5001)
 ---> System.Net.Sockets.SocketException (0xFFFDFFFF): Name or service not known
   at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.ThrowException(SocketError error, CancellationToken cancellationToken)
   at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.System.Threading.Tasks.Sources.IValueTaskSource.GetResult(Int16 token)
   at System.Net.Sockets.Socket.<ConnectAsync>g__WaitForConnectWithCancellation|277_0(AwaitableSocketAsyncEventArgs saea, ValueTask connectTask, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.ConnectToTcpHostAsync(String host, Int32 port, HttpRequestMessage initialRequest, Boolean async, CancellationToken cancellationToken)
   --- End of inner exception stack trace ---
   at System.Net.Http.HttpConnectionPool.ConnectToTcpHostAsync(String host, Int32 port, HttpRequestMessage initialRequest, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.AddHttp11ConnectionAsync(HttpRequestMessage request)
   at System.Threading.Tasks.TaskCompletionSourceWithCancellation`1.WaitWithCancellationAsync(CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.GetHttp11ConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.SendWithVersionDetectionAndRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken)
   at System.Net.Http.DiagnosticsHandler.SendAsyncCore(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpClient.<SendAsync>g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)
   at Microsoft.IdentityModel.Protocols.HttpDocumentRetriever.GetDocumentAsync(String address, CancellationToken cancel)
   --- End of inner exception stack trace ---
   at Microsoft.IdentityModel.Protocols.HttpDocumentRetriever.GetDocumentAsync(String address, CancellationToken cancel)
   at Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectConfigurationRetriever.GetAsync(String address, IDocumentRetriever retriever, CancellationToken cancel)
   at Microsoft.IdentityModel.Protocols.ConfigurationManager`1.GetConfigurationAsync(CancellationToken cancel)
   --- End of inner exception stack trace ---
   at Microsoft.IdentityModel.Protocols.ConfigurationManager`1.GetConfigurationAsync(CancellationToken cancel)
   at Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.HandleAuthenticateAsync()
2025-02-07 15:07:10.1103 [App:01] [9f7d1ff3e0fd3afff9493a7fdf037843] [ERROR] [.NET ThreadPool Worker] Microsoft.AspNetCore.Server.Kestrel - Connection id "0HNA7H3LABFC0", Request id "0HNA7H3LABFC0:00000002": An unhandled exception was thrown by the application. System.InvalidOperationException: IDX20803: Unable to obtain configuration from: 'System.String'.
 ---> System.IO.IOException: IDX20804: Unable to retrieve document from: 'System.String'.
 ---> System.Net.Http.HttpRequestException: Name or service not known (pk-produkter.localtest.me:5001)
 ---> System.Net.Sockets.SocketException (0xFFFDFFFF): Name or service not known
   at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.ThrowException(SocketError error, CancellationToken cancellationToken)
   at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.System.Threading.Tasks.Sources.IValueTaskSource.GetResult(Int16 token)
   at System.Net.Sockets.Socket.<ConnectAsync>g__WaitForConnectWithCancellation|277_0(AwaitableSocketAsyncEventArgs saea, ValueTask connectTask, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.ConnectToTcpHostAsync(String host, Int32 port, HttpRequestMessage initialRequest, Boolean async, CancellationToken cancellationToken)
   --- End of inner exception stack trace ---
   at System.Net.Http.HttpConnectionPool.ConnectToTcpHostAsync(String host, Int32 port, HttpRequestMessage initialRequest, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.AddHttp11ConnectionAsync(HttpRequestMessage request)
   at System.Threading.Tasks.TaskCompletionSourceWithCancellation`1.WaitWithCancellationAsync(CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.GetHttp11ConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.SendWithVersionDetectionAndRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken)
   at System.Net.Http.DiagnosticsHandler.SendAsyncCore(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpClient.<SendAsync>g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)
   at Microsoft.IdentityModel.Protocols.HttpDocumentRetriever.GetDocumentAsync(String address, CancellationToken cancel)
   --- End of inner exception stack trace ---
   at Microsoft.IdentityModel.Protocols.HttpDocumentRetriever.GetDocumentAsync(String address, CancellationToken cancel)
   at Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectConfigurationRetriever.GetAsync(String address, IDocumentRetriever retriever, CancellationToken cancel)
   at Microsoft.IdentityModel.Protocols.ConfigurationManager`1.GetConfigurationAsync(CancellationToken cancel)
   --- End of inner exception stack trace ---
   at Microsoft.IdentityModel.Protocols.ConfigurationManager`1.GetConfigurationAsync(CancellationToken cancel)
   at Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.HandleAuthenticateAsync()
   at Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.HandleAuthenticateAsync()
   at Microsoft.AspNetCore.Authentication.AuthenticationHandler`1.AuthenticateAsync()
   at Microsoft.AspNetCore.Authentication.AuthenticationService.AuthenticateAsync(HttpContext context, String scheme)
   at Microsoft.AspNetCore.Authorization.Policy.PolicyEvaluator.AuthenticateAsync(AuthorizationPolicy policy, HttpContext context)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ProcessRequests[TContext](IHttpApplication`1 application)

litium.log:

2025-02-07 11:52:37.4562 [App:01] [fd590b0e3dad2e06f27244d5831b3a5c] [ERROR] [.NET ThreadPool Worker] Litium.Web.Administration.Web>
   at Litium.Application.AppManagement.AppManagementServiceImpl.GetMetadataAsync(String url)
   at Litium.Web.Administration.WebApi.Settings.Controllers.AppManagementController.InstallGet(String url)

I had to configure a bit more to make dns work properly and this is my docker-compose.yml:

# Docker compose below sets up the containers needed to run Litium locally:

networks:
  custom_bridge:
    ipam:
      driver: default
      config:
        - subnet: 192.168.100.0/24

services:
  dnsresolver:
    # https://github.com/cytopia/docker-bind
    container_name: dnsresolver
    image: cytopia/bind:stable-0.28
    ports:
    - "53:53/tcp"
    - "53:53/udp"
    networks:
      custom_bridge:
        ipv4_address: 192.168.100.53
    environment: 
    - DNS_CNAME=*.localtest.me=host.docker.internal
    - DNS_FORWARDER=8.8.8.8 # Should point to a DNS Server for Docker Desktop its 192.168.65.7 depending on version.
    dns: 
      - 8.8.8.8
    restart: unless-stopped

  netshoot:
    container_name: netshoot
    image: nicolaka/netshoot
    dns: 192.168.100.53
    networks: 
      - custom_bridge
    command: ["sleep", "infinity"]  # Keeps the container running
    restart: unless-stopped
  
  elasticsearch:
    container_name: elastic
    image: docker.elastic.co/elasticsearch/elasticsearch:7.6.2
    depends_on:
    - dnsresolver
    dns: 
    - 192.168.100.53
    networks: 
      - custom_bridge
    restart: unless-stopped
    user: root
    ports:
    - "9200:9200"
    environment:
    - discovery.type=single-node
    # Allocate 2GB RAM instead of the default 512MB
    # comment out the line below for additional memory allocation
    # - "ES_JAVA_OPTS=-Xms2g -Xmx2g"
    volumes:
    - ./volumes/elasticsearch/data:/usr/share/elasticsearch/data
    entrypoint: 
    - /bin/sh
    - -c
    # The accelerator implementation of Elasticsearch require the analysis-dynamic-synonym.
    # The plugin refreshes the list of synonyms in Elasticsearch every minute allowing synonyms 
    # to be added/modified in Litium backoffice and updated in Elasticsearch without downtime.
    # Internet access is not working so we try to install the plugin by downloading it seperatly and placing it in /data/plugin manually
    # The original link to zip is https://github.com/Tasteful/elasticsearch-analysis-dynamic-synonym/releases/download/v7.6.2/elasticsearch-analysis-dynamic-synonym.zip
    - "chown -R 1000:1000 /usr/share/elasticsearch/data &&./bin/elasticsearch-plugin list | grep -q analysis-dynamic-synonym || ./bin/elasticsearch-plugin install -b file:///usr/share/elasticsearch/data/plugin/elasticsearch-analysis-dynamic-synonym.zip; /usr/local/bin/docker-entrypoint.sh"
 
  kibana:
    container_name: kibana
    # The Kibana image tries, by default, to connect to a host/container called elasticsearch.
    image: docker.elastic.co/kibana/kibana:7.6.2
    depends_on:
    - elasticsearch
    networks: 
      - custom_bridge
    restart: unless-stopped
    ports:
    - "5601:5601"

  synonymserver:
    container_name: synonym
    # Synonym server to provide elasticsearch with synonyms.
    image: registry.litium.cloud/apps/synonym-server:1.2.0
    restart: unless-stopped
    ports:
    - "9210:80"
    networks: 
      - custom_bridge
    environment:
    - DataFolder=/app_data
    volumes:
    - ./volumes/synonymserver/data:/app_data

  redis:
    container_name: redis
    image: redis:5.0.5-alpine
    restart: unless-stopped
    ports:
    - "6379:6379"
    networks: 
      - custom_bridge

  mailhog:
    container_name: mailhog
    image: mailhog/mailhog:latest
    restart: unless-stopped
    logging:
      driver: 'none'
    ports:
      - "1025:1025" # SMTP-server
      - "8025:8025" # Web UI
    networks: 
      - custom_bridge

  sqlserver:
    container_name: sqlserver
    image: mcr.microsoft.com/mssql/server:2019-latest
    environment:
      - SA_PASSWORD=Pass@word
      - ACCEPT_EULA=Y
    restart: unless-stopped
    user: root
    ports:
      # Make the SQL Container available on port 5434 to not conflict with a previously installed local SQL instance.
      # If you do not have SQL Server installed you can use 1433:1433 as mapping and skip port number in connectionstrings.
      - "5434:1433"
    networks: 
      - custom_bridge
    volumes:
      # Map [local directory:container directory] - this is so that db/log files are
      # stored on the "host" (your local computer, outside of container) and thereby 
      # persisted when container restarts.
      # by starting local path with "." it gets relative to current folder, meaning that the database
      # files will be on your computer in the same directory as you have this docker-compose.yaml file
      - ./data/mssql/data:/var/opt/mssql/data
      - ./data/mssql/log:/var/opt/mssql/log
    entrypoint: 
      # Due to an issue with the sqlserver image, permissions to db-files may be lost on container restart
      # by using the specific permissions_check entrypoint you assert that permissions are set on every restart
      - /bin/sh
      - -c
      - "/opt/mssql/bin/permissions_check.sh && /opt/mssql/bin/sqlservr"

  briqpay-payment:
    container_name: briqpay
    image: registry.litium.cloud/apps/briqpay-payment:1.9.4
    restart: unless-stopped
    ports:
      - "10030:80"
      - "10031:443"
    networks: 
      - custom_bridge
    dns: 192.168.100.53
    environment:
      # Enable HTTPS binding
      - ASPNETCORE_URLS=https://+;http://+
      - ASPNETCORE_HTTPS_PORT=10031
      # Configuration for HTTPS inside the container, exported dotnet dev-certs with corresponding password
      - ASPNETCORE_Kestrel__Certificates__Default__Password=SuperSecretPassword
      - ASPNETCORE_Kestrel__Certificates__Default__Path=/https/localhost.pfx
      # Folder for the configuraiton, this is volume-mapped
      - CONFIG_PATH=/app_config
      # Folder where logfiles should be placed, this is volume-mapped
      - APP_LOG_PATH=/logs
      # Don't validate certificates
      - AppConfiguration__ValidateCertificate=false
      # Disable callbacks
      - AppConfiguration__DisableCallbacks=true
      # Url to this app
      - AppMetadata__AppUrl=https://briqpay-app.localtest.me:10031
      # Url to the litium installation
      - LitiumApi__ApiUrl=https://pk-produkter.localtest.me:5001
    volumes:
      - ./data/briqpay-payment/config:/app_config
      - ./data/briqpay-payment/data:/app_data
      - ./data/briqpay-payment/logs:/logs
      - ./data/briqpay-payment/DataProtection-Keys:/root/.aspnet/DataProtection-Keys
      - ./data/https:/https:ro

  direct-payment:
    container_name: payment
    image: registry.litium.cloud/apps/direct-payment:1.4.1
    restart: unless-stopped
    ports:
    - "10010:80"
    - "10011:443"
    networks: 
      - custom_bridge
    dns: 
      - 192.168.100.53
    environment:
    # Enable HTTPS binding
    - ASPNETCORE_URLS=https://+;http://+
    - ASPNETCORE_HTTPS_PORT=10011
    # Configuration for HTTPS inside the container, exported dotnet dev-certs with corresponding password
    - ASPNETCORE_Kestrel__Certificates__Default__Password=SuperSecretPassword
    - ASPNETCORE_Kestrel__Certificates__Default__Path=/https/localhost.pfx
    # Folder for the configuraiton, this is volume-mapped
    - CONFIG_PATH=/app_config
    # Folder where logfiles should be placed, this is volume-mapped
    - APP_LOG_PATH=/logs
    # Don't validate certificates
    - AppConfiguration__ValidateCertificate=false
    # Url to this app
    - AppMetadata__AppUrl=https://payment-app.localtest.me:10011
    # Url to the litium installation
    - LitiumApi__ApiUrl=https://pk-produkter.localtest.me:5001
    volumes:
    - ./data/direct-payment/config:/app_config
    - ./data/direct-payment/data:/app_data
    - ./data/direct-payment/logs:/logs
    - ./data/direct-payment/DataProtection-Keys:/root/.aspnet/DataProtection-Keys
    - ./data/https:/https:ro

  direct-shipment:
    container_name: shipment
    image: registry.litium.cloud/apps/direct-shipment:1.2.0
    ports:
    - "10020:80"
    - "10021:443"
    networks: 
      - custom_bridge
    dns: 
    - 192.168.100.53
    environment:
    # Enable HTTPS binding
    - ASPNETCORE_URLS=https://+;http://+
    - ASPNETCORE_HTTPS_PORT=10021
    # Configuration for HTTPS inside the container, exported dotnet dev-certs with corresponding password
    - ASPNETCORE_Kestrel__Certificates__Default__Password=SuperSecretPassword
    - ASPNETCORE_Kestrel__Certificates__Default__Path=/https/localhost.pfx
    # Folder for the configuraiton, this is volume-mapped
    - CONFIG_PATH=/app_config
    # Folder where logfiles should be placed, this is volume-mapped
    - APP_LOG_PATH=/logs
    # Don't validate certificates
    - AppConfiguration__ValidateCertificate=false
    # Url to this app
    - AppMetadata__AppUrl=https://shipment-app.localtest.me:10021
    # Url to the litium installation
    - LitiumApi__ApiUrl=https://pk-produkter.localtest.me:5001
    volumes:
    - ./data/direct-shipment/config:/app_config
    - ./data/direct-shipment/data:/app_data
    - ./data/direct-shipment/logs:/logs
    - ./data/direct-shipment/DataProtection-Keys:/root/.aspnet/DataProtection-Keys
    - ./data/https:/https:ro

Is there any one out there who have good knowledge about docker and network?

I think that my problem is that the app cant talk back to the host eg the Litium Accelerator.

Litium version: 8.12

I agree to that I guess that the problem is in communication back from the container to Litium and this based on the the

Name or service not known (pk-produkter.localtest.me:5001)

from the log.

you have set the dns resolver to 8.8.8.8 (Google if I remember correct) and I guess that can not result the host.docker.internal from the line above.

If you instead change the DNS_CNAME=*.localtest.me=host.docker.internal to something like DNS_A=*.localtest.me=192.168.1.1 where you change the 192.168.1.1 to your computers IP you may get so the DNS resolving will work but you have then for every new IP you are getting change that, that is the reason it was written to use host.docker.internal and using DNS Server for Docker Desktop because that will; for every restart, set the correct IP for that hostname.

This is amazing! Thank you Patric, Apps are installed and working as they should. This is going in my documentation ASAP!

1 Like