When we want to deploy an ASP.NET Core app to the cloud, the usual candidate would be Azure. What if we want to deploy to all other PAAS providers that do not have native .NET Core support (e.g., Render)? Or an IAAS provider that has a Linux VM? Docker can come to the rescue in these scenarios.
In this blog post, we will build our ASP.NET Core app as a Docker container to publish in Docker Hub. Specifically, we will make sure the container is correctly targeting the ARM Ampere compute instance we are using in Oracle Cloud Infrastructure (OCI). We will then run it as a container image in the Oracle Linux VM on this ARM Compute instance.
I’ll assume that the reader has at least completed the first 4 steps in my previous post .
Setup Docker in Oracle VM
In this part, we will install Docker and setup network connectivity. We will configure new ingress rules in our VM subnet’s Security List, to open up ports for our Docker containers.
setup connectivity
Each VM is associated with a public subnet in a Virtual Cloud Network. Follow the steps to add a new ingress rule for this subnet. This will be used to map a port on the VM to the container.
- Source CIDR: 0.0.0.0/0 (open to all clients)
- IP Protocol: TCP
- Source Port Range: Leave blank (all ports)
- Destination Port Range: Enter whatever you like, in my case I enter 7777-7778
- Click Add Ingress Rule to save
install Docker
We will now install Docker engine in our Oracle Linux VM. For Ubuntu, follow these offical steps .
Ensure our system’s packages are up to date
sudo dnf update -y
Install necessary utilities and add the official Docker repository for RHEL (which is compatible with Oracle Linux)
sudo dnf install -y yum-utils sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
Install Docker Engine
Now we can install Docker Engine, CLI, containerd , and Docker Compose from the newly added repository:
sudo dnf install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Enable and start the Docker service
sudo systemctl enable docker sudo systemctl start docker
If you want to start and enable the Docker service to run on boot, use
sudo systemctl enable --now docker
.
Verify the Docker installation
Check the Docker service status with
sudo systemctl status docker
.To confirm the installation, download a test image and run it in a container. A confirmation message indicates a successful installation.
sudo docker run hello-world
Manage Docker as a non-root user
By default, only the root user can run Docker commands. To allow a regular user to manage Docker, add them to the docker group with
sudo usermod -aG docker your_username
, replacingyour_username
with the actual username.Or use
sudo usermod -aG docker $USER
to add the current user to thedocker
group.Log off and log back in for this change to take effect.
To check network connectivity of Docker containers in our VM, we can validate with the default
nginx
container as it comes with a test page that can be accessed via our VM’s public IP.To do this, we will run it to listen on port 80, mapped to port 7778 in the VM. Remember 7778 is the Destination Port we have chosen in the previous section when setting up a new ingress rule. Substitute it for whatever port number you’ve chosen.
docker run --name mynginx -p 7778:80 -d nginx
Here’s a breakdown of the command:
docker run
: Creates a new container and runs a command in it.--name mynginx
: Assigns a name to your container, in this case, “mynginx”. This makes it easier to refer to and manage the container later.-p 7778:80
: This option maps port7778
on our host machine to port80
inside the container. NGINX typically listens for web traffic on port 80.-d
: This flag runs the container in “detached” mode, meaning it runs in the background and doesn’t tie up our terminal.nginx
: Specifies the Docker image to use. In this case, it’s the official NGINX image from Docker Hub.
After running the command, Docker will download the NGINX image (if it’s not found locally) and start a new container running it.
Use
docker ps
to verify if the container is running.Open a browser and hit the VM’s public IP at port 7778, e.g.,
http://VM_PUBLIC_IP:7778
. We should see NGINX’s welcome page.
Deploy ASP.NET Core app to Oracle VM
Now that we have prepared our VM, let’s build our image so we can deploy to our VM.
build & publish image
Prereq: Make sure Docker Desktop is installed. If you’re on Windows, you must enable WSL2 too.
Since I’m running the VM on the ARM Ampere Compute shape in OCI, I need to build a Docker image specifically for this platform.
In Visual Studio, you can use the Publish feature to publish to Docker Hub. VS will automatically compile the project and issue a Docker command to build and push an image. It works fine if your host architecture is the same as your target’s. For example, if your dev machine is x86 and you’re planning to deploy the container to an x86 platform.
However, it doesn’t work if the host/target arch are different. In my case, I assumed setting the Publish dialog’s Show All Settings/Target Runtime to linux/arm64 will take care of it. Instead, the built Docker image still defaults to linux/amd which will fail in our Oracle Ampere VM. This is because Target Runtime only sets what binaries VS compiles for, and has no effect on what architecture the container can run on.
In fact, VS will use the default docker builder which only builds for the host architecture (e.g., amd64 on most PCs). This can be verified in VS’s Output window, in which the following command is issued:
"C:\Program Files\Docker\Docker\resources\bin\docker.exe" build -f "<PROJECT PATH>\\Dockerfile" --force-rm -t projName --label "com.microsoft.created-by=visual-studio" --label "com.microsoft.visual-studio.project-name=projName" --build-arg "BUILD_CONFIGURATION=Release" ""<PROJECT PATH>\\projName"
When I pull the image from my Oracle Ampere instance later, it gives a warning “The requested image’s platform (linux/amd64) does not match the detected host platform (linux/arm64/v8)”.
The correct command to build for a specific platform (arm64
in our case) is to skip VS Publish, and manually use the newer docker buildx
with the --platform
flag:
docker buildx build -f "<PROJECT PATH>\\Dockerfile" --platform linux/arm64 -t yourrepo/projName --push .
-f
points to the Dockerfile (created automatically by Add Docker Support in VS Solution Explorer and choose a Target OS)--platform linux/arm64
forces it to target this platform-- push
will push the image to Docker Hub when done
pull & run image
In Oracle VM, verify the image’s supported platform
Inspect its manifest list to ensure the build process is successful:
docker manifest inspect yourrepo/projName
Look for an entry with
platform
set tolinux/arm64
orlinux/arm64/v8
. Or to jump right to the platform information, rundocker image inspect yourrepo/projName --format '{{.Architecture}}'
Expected value is
arm64
.Pull image from Docker Hub
docker pull yourrepo/projName
Discover which port the image listens to
docker image inspect yourrepo/projName | grep -A2 ExposedPorts
For example, I get
"ExposedPorts": { "8080/tcp": {}, "8081/tcp": {}
This means the app listens on port 8080 and 8081 inside the container.
Run the container
Pick a host port you configured in the New Ingress Rule above (in my case, 7777) and map it to the container’s app port (replace <CONTAINER_PORT> with what you found in the previous step).
docker run -d --name projName \ -p 7777:<CONTAINER_PORT> \ -v /var/projName/data:/data \ --restart unless-stopped \ yourrepo/projName
The
-v
flag is optional for specifying a volume to preserve data if the image supports persistence (e.g., configs or indexes).The
--restart
controlls the restart policy after a VM reboot.unless-stopped
will make sure it will restart after a crash or VM reboot, but not after you manually stop the container.We can also feed environment variables to the image with
-e KEY=value
or batch add with a file using--env-file /path/to/file
. When the container is up and running, we can check if the environment variable is passed correctly to the container by listing all of them:docker exec <container_id_or_name> env
Now we can hit our ASP.NET Core app in the container by
http://VM_PUBLIC_IP:7777
!
Final thoughts
In this post, we have taken an ASP.NET Core project and manually build a Docker container targeting Linux on ARM64 architecture. We have setup the right connectivity options in an Oracle ARM-based Ampere instance. Finally, we’ve deployed the container as an image and accessed it successfully from public.
For the container to work in an architecture different from your dev machine, the only trick is to ensure we use the newer buildx
Docker builder which supports building for specific and multiple architectures.
Comments