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
| Requirement | Why |
|---|---|
| Long-running HTTP process | The container must keep running to serve requests |
Listen on 0.0.0.0 | Connections come from outside the container |
| Port equal to the Port field | Any port works, including 80 |
| Configuration through environment variables | Secrets and URLs change without rebuilding the image |
| Versioned tags | 1.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
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"]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
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 bootJarand thebuild/libs/*.jarpath. - Spring Boot listens on
8080by default. To change it, use theSERVER_PORTvariable. -XX:MaxRAMPercentage=75makes 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, andSPRING_DATASOURCE_PASSWORD. The URL uses JDBC format, such asjdbc:postgresql://host:5432/db. - With Actuator, use
/actuator/healthas the health check path.
Laravel
The serversideup/php image already ships PHP-FPM and NGINX and listens on 8080:
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 8080With AUTORUN_ENABLED=true, the image runs php artisan migrate --force and the config, route, and view caches at startup.
| Variable | Value |
|---|---|
APP_KEY | Output of php artisan key:generate --show |
APP_ENV | production |
APP_DEBUG | false |
APP_URL | https://your-project.clients.zenifra.com or your domain |
DB_CONNECTION, DB_HOST, DB_PORT, DB_DATABASE, DB_USERNAME, DB_PASSWORD | Details 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
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.0Macs 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:
- Create Project → OCI Image.
- Enter
ghcr.io/your-org/my-api:1.0.0. - If the image is private, configure credentials in Private registry.
- 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/healthIf the image responds locally with the same variables as the project, it will respond on Zenifra.
Common problems
| Symptom | Fix |
|---|---|
exec format error | Image built for ARM. Use --platform linux/amd64 |
permission denied when writing files | Give the image user access to the folder or use /tmp |
| Project cannot pull the image | Tag does not exist or private registry lacks credentials |
| JVM killed out of memory | Use -XX:MaxRAMPercentage or a bigger plan |
Next steps
Last updated on