Framework guides

Ready-made Dockerfiles for Go, Java, PHP, and .NET

Languages other than Node.js and Python are deployed as an OCI Image. This page provides ready-made Dockerfiles that meet Zenifra's requirements, plus the commands to build and publish the image.

Requirements for every image

RequirementWhy
Long-running HTTP processThe container must keep running to serve requests
Listen on 0.0.0.0Connections come from outside the container
Port equal to the Port fieldAny port works, including 80
Configuration through environment variablesSecrets and URLs change without rebuilding the image
Versioned tags1.4.2 or the commit SHA make rollbacks easy; avoid latest

Images that run as root and listen on low ports such as 80 work normally. The examples below use an unprivileged user as a security best practice, not a requirement, and listen on port 8080. Set the project Port field to the same value.

Go

Dockerfile
FROM golang:1.23 AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o /app ./cmd/server

FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=build /app /app
EXPOSE 8080
ENTRYPOINT ["/app"]
Listening on the port
port := os.Getenv("PORT")
if port == "" {
    port = "8080"
}
log.Fatal(http.ListenAndServe(":"+port, mux))

Change ./cmd/server to your project's main package. The distroless image contains only the binary, with no shell, which keeps the image small with less attack surface.

Spring Boot

Dockerfile
FROM eclipse-temurin:21-jdk AS build
WORKDIR /src
COPY . .
RUN ./mvnw -q -DskipTests package && cp target/*.jar /app.jar

FROM eclipse-temurin:21-jre
RUN useradd --system --uid 10001 app
COPY --from=build /app.jar /app/app.jar
USER 10001
EXPOSE 8080
ENTRYPOINT ["java", "-XX:MaxRAMPercentage=75", "-jar", "/app/app.jar"]
  • With Gradle, use ./gradlew bootJar and the build/libs/*.jar path.
  • Spring Boot listens on 8080 by default. To change it, use the SERVER_PORT variable.
  • -XX:MaxRAMPercentage=75 makes the JVM respect the plan's memory instead of sizing itself for the whole server.
  • Configure the database with SPRING_DATASOURCE_URL, SPRING_DATASOURCE_USERNAME, and SPRING_DATASOURCE_PASSWORD. The URL uses JDBC format, such as jdbc:postgresql://host:5432/db.
  • With Actuator, use /actuator/health as the health check path.

Laravel

The serversideup/php image already ships PHP-FPM and NGINX and listens on 8080:

Dockerfile
FROM serversideup/php:8.3-fpm-nginx
WORKDIR /var/www/html
COPY --chown=www-data:www-data . .
RUN composer install --no-dev --optimize-autoloader --no-interaction
ENV AUTORUN_ENABLED=true
EXPOSE 8080

With AUTORUN_ENABLED=true, the image runs php artisan migrate --force and the config, route, and view caches at startup.

VariableValue
APP_KEYOutput of php artisan key:generate --show
APP_ENVproduction
APP_DEBUGfalse
APP_URLhttps://your-project.clients.zenifra.com or your domain
DB_CONNECTION, DB_HOST, DB_PORT, DB_DATABASE, DB_USERNAME, DB_PASSWORDDetails from the managed database

For Laravel to generate https URLs, configure trustProxies(at: '*') in bootstrap/app.php. If the front end uses Vite, add a Node.js stage that runs npm ci && npm run build before the final image.

Files in storage/app disappear on every deployment. Use persistent storage with the /var/www/html/storage/app directory or an object storage service.

ASP.NET Core

Dockerfile
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY *.csproj ./
RUN dotnet restore
COPY . .
RUN dotnet publish -c Release -o /out --no-restore

FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
COPY --from=build /out .
USER $APP_UID
EXPOSE 8080
ENTRYPOINT ["dotnet", "MyApi.dll"]

.NET 8 images listen on 8080 by default and already include an unprivileged user ($APP_UID). Replace MyApi.dll with your project name. Connection strings use variables such as ConnectionStrings__Default.

Publish the image

Build the image for linux/amd64 and push it to a registry. Example with GitHub Container Registry:

echo $GITHUB_TOKEN | docker login ghcr.io -u YOUR_USER --password-stdin
docker build --platform linux/amd64 -t ghcr.io/your-org/my-api:1.0.0 .
docker push ghcr.io/your-org/my-api:1.0.0

Macs with Apple Silicon

Without --platform linux/amd64, Docker on M1/M2/M3 Macs builds an ARM image, which does not start on Zenifra.

Then, in the console:

  1. Create Project → OCI Image.
  2. Enter ghcr.io/your-org/my-api:1.0.0.
  3. If the image is private, configure credentials in Private registry.
  4. Port 8080, variables, and plan.

To automate build, push, and deployment, use the Zenifra GitHub Action.

Test the image locally first

docker run --rm -p 8080:8080 -e PORT=8080 ghcr.io/your-org/my-api:1.0.0
curl http://localhost:8080/health

If the image responds locally with the same variables as the project, it will respond on Zenifra.

Common problems

SymptomFix
exec format errorImage built for ARM. Use --platform linux/amd64
permission denied when writing filesGive the image user access to the folder or use /tmp
Project cannot pull the imageTag does not exist or private registry lacks credentials
JVM killed out of memoryUse -XX:MaxRAMPercentage or a bigger plan

Next steps

Last updated on

On this page