In most of my projects the Jenkinsfile looks more or less like:

def STAGE_URL='...'
def STAGE_DEPLOY_HOSTNAME='...'
...
def PROD_URL='...'
def PROD_DEPLOY_HOSTNAME='...'
...
pipeline {
agent {
label '... && ... && ... && ...'
}
options {
skipDefaultCheckout true
}
stages {
stage('Configure') {
steps {
script {
if (env.BRANCH_NAME == 'master') {
currentBuild.displayName = "1.0.${BUILD_NUMBER}"
}
else {
currentBuild.displayName = "non-master-build-${BUILD_NUMBER}"
}
}
}
}
stage('Checkout') {
steps {
// deleteDir() /* clean up our workspace before checkout */
checkout scm
}
}
stage('Build') {
steps {
// ...
}
}
stage('Test') {
steps {
// ...
}
}
stage('Deploy') {
when {
branch 'master'
}
stages {
stage('Stage') {
stages {
stage('Service deploy') {
options {
lock('...-stage-deploy')
}
steps {
// ...
}
}
}
}
stage('Prod') {
input {
message "Deploy service to production?"
submitter 'Administrators'
}
stages {
stage('Service deploy') {
options {
lock('...-prod-deploy')
}
steps {
// ...
}
}
}
}
stage('Package publishing') {
input {
message "Publish client libraries?"
submitter 'Administrators'
}
steps {
// ...
}
}
}
}
}
}

Well, omit sections that are not relevant to your project.

Why ‘Deploy’ is so deep in levels?

In a declarative pipeline, unfortunately, we can’t combine ‘when’, ‘input’ and ‘lock’ in a reasonable way (check for branch, then ask for confirmation and then lock for deploy) using less levels.

What ‘Checkout’ stage is for?

You may need to build in a clean workspace, but keep it for an investigation after the build. So, post condition is not applicable and you have to clean up before checkout.

What for to use definitions?

You can parametrize something in def …=’…’ to not spread things all over the file. I don’t insist it to be a URL or hostname.